From 69bb65e47bd3c6dcd37beb1535f24034274fb339 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 13 Aug 2023 16:51:15 +0100 Subject: [PATCH 001/451] Allow bookart to be in texutres and texutres to be in bookart. Rebased to account for upstream normalising slashes to turn forward slashes into backslashes. This simplifies some conditions that previously needed to check for both kinds. --- components/misc/resourcehelpers.cpp | 26 ++++++++++++++++---------- components/misc/resourcehelpers.hpp | 4 ++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index ce552df4f7..762a8b88d2 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -46,8 +46,8 @@ bool Misc::ResourceHelpers::changeExtensionToDds(std::string& path) return changeExtension(path, ".dds"); } -std::string Misc::ResourceHelpers::correctResourcePath( - std::string_view topLevelDirectory, std::string_view resPath, const VFS::Manager* vfs) +std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevelDirectory, std::string_view resPath, + const VFS::Manager* vfs, const std::vector& alternativeDirectories) { /* Bethesda at some point converted all their BSA * textures from tga to dds for increased load speed, but all @@ -69,12 +69,18 @@ std::string Misc::ResourceHelpers::correctResourcePath( if (!correctedPath.starts_with(topLevelDirectory) || correctedPath.size() <= topLevelDirectory.size() || correctedPath[topLevelDirectory.size()] != '\\') { - std::string topLevelPrefix = std::string{ topLevelDirectory } + '\\'; - size_t topLevelPos = correctedPath.find('\\' + topLevelPrefix); - if (topLevelPos == std::string::npos) - correctedPath = topLevelPrefix + correctedPath; - else - correctedPath.erase(0, topLevelPos + 1); + bool needsPrefix = true; + for (std::string_view alternativeDirectory : alternativeDirectories) + { + if (!correctedPath.starts_with(alternativeDirectory) || correctedPath.size() <= alternativeDirectory.size() + || correctedPath[alternativeDirectory.size()] != '\\') + { + needsPrefix = false; + break; + } + } + if (needsPrefix) + correctedPath = std::string{ topLevelDirectory } + '\\' + correctedPath; } std::string origExt = correctedPath; @@ -110,7 +116,7 @@ std::string Misc::ResourceHelpers::correctResourcePath( std::string Misc::ResourceHelpers::correctTexturePath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath("textures", resPath, vfs); + return correctResourcePath("textures", resPath, vfs, { "bookart" }); } std::string Misc::ResourceHelpers::correctIconPath(std::string_view resPath, const VFS::Manager* vfs) @@ -120,7 +126,7 @@ std::string Misc::ResourceHelpers::correctIconPath(std::string_view resPath, con std::string Misc::ResourceHelpers::correctBookartPath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath("bookart", resPath, vfs); + return correctResourcePath("bookart", resPath, vfs, { "textures" }); } std::string Misc::ResourceHelpers::correctBookartPath( diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index 478569ed14..f45a52a23c 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -22,8 +22,8 @@ namespace Misc namespace ResourceHelpers { bool changeExtensionToDds(std::string& path); - std::string correctResourcePath( - std::string_view topLevelDirectory, std::string_view resPath, const VFS::Manager* vfs); + std::string correctResourcePath(std::string_view topLevelDirectory, std::string_view resPath, + const VFS::Manager* vfs, const std::vector& alternativeDirectories = {}); std::string correctTexturePath(std::string_view resPath, const VFS::Manager* vfs); std::string correctIconPath(std::string_view resPath, const VFS::Manager* vfs); std::string correctBookartPath(std::string_view resPath, const VFS::Manager* vfs); From 575367bc18bfd030335166deedef58bd680a1c54 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 13 Aug 2023 23:35:25 +0100 Subject: [PATCH 002/451] v e c t o r --- components/misc/resourcehelpers.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index f45a52a23c..dc0b487a59 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace VFS { From f30676cbc73b9ee5135f93a3567753a89d684831 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 14 Aug 2023 14:10:45 +0100 Subject: [PATCH 003/451] Invert condition Rebased to account for upstream normalising slashes to replace forward slashes with backslashes, simplifying the part that needed to check for both variants. Perhaps if it'd been like that in the first place, I wouldn't have made the mistake that made the original version of this commit necessary. --- components/misc/resourcehelpers.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 762a8b88d2..32f65872c7 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -72,8 +72,8 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevel bool needsPrefix = true; for (std::string_view alternativeDirectory : alternativeDirectories) { - if (!correctedPath.starts_with(alternativeDirectory) || correctedPath.size() <= alternativeDirectory.size() - || correctedPath[alternativeDirectory.size()] != '\\') + if (correctedPath.starts_with(alternativeDirectory) && correctedPath.size() > alternativeDirectory.size() + && correctedPath[alternativeDirectory.size()] == '\\') { needsPrefix = false; break; From bf0756c5b35536949b2026d41b5ebdb3aa3c6a42 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 14 Aug 2023 22:09:51 +0100 Subject: [PATCH 004/451] c h a n g e l o g Rebased to account for other new changelog entries. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b36eb2525..dd6f7d68c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,7 @@ 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 #7535: Bookart paths for textures in OpenMW vs vanilla Morrowind Bug #7553: Faction reaction loading is incorrect Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading Bug #7573: Drain Fatigue can't bring fatigue below zero by default From 3a71a78d9ea32708b996b7f51fe7717ca3ad7316 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 10 Dec 2023 19:01:30 +0000 Subject: [PATCH 005/451] Combine topLevelDirectory and alternativeDirectories --- components/misc/resourcehelpers.cpp | 34 +++++++++++++---------------- components/misc/resourcehelpers.hpp | 4 ++-- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 32f65872c7..b602001913 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -46,8 +46,8 @@ bool Misc::ResourceHelpers::changeExtensionToDds(std::string& path) return changeExtension(path, ".dds"); } -std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevelDirectory, std::string_view resPath, - const VFS::Manager* vfs, const std::vector& alternativeDirectories) +std::string Misc::ResourceHelpers::correctResourcePath( + const std::vector& topLevelDirectories, std::string_view resPath, const VFS::Manager* vfs) { /* Bethesda at some point converted all their BSA * textures from tga to dds for increased load speed, but all @@ -66,22 +66,18 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevel correctedPath.erase(0, 1); // Handle top level directory - if (!correctedPath.starts_with(topLevelDirectory) || correctedPath.size() <= topLevelDirectory.size() - || correctedPath[topLevelDirectory.size()] != '\\') + bool needsPrefix = true; + for (std::string_view alternativeDirectory : topLevelDirectories) { - bool needsPrefix = true; - for (std::string_view alternativeDirectory : alternativeDirectories) + if (correctedPath.starts_with(alternativeDirectory) && correctedPath.size() > alternativeDirectory.size() + && correctedPath[alternativeDirectory.size()] == '\\') { - if (correctedPath.starts_with(alternativeDirectory) && correctedPath.size() > alternativeDirectory.size() - && correctedPath[alternativeDirectory.size()] == '\\') - { - needsPrefix = false; - break; - } + needsPrefix = false; + break; } - if (needsPrefix) - correctedPath = std::string{ topLevelDirectory } + '\\' + correctedPath; } + if (needsPrefix) + correctedPath = std::string{ topLevelDirectories.front() } + '\\' + correctedPath; std::string origExt = correctedPath; @@ -96,7 +92,7 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevel return origExt; // fall back to a resource in the top level directory if it exists - std::string fallback{ topLevelDirectory }; + std::string fallback{ topLevelDirectories.front() }; fallback += '\\'; fallback += getBasename(correctedPath); if (vfs->exists(fallback)) @@ -104,7 +100,7 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevel if (changedToDds) { - fallback = topLevelDirectory; + fallback = topLevelDirectories.front(); fallback += '\\'; fallback += getBasename(origExt); if (vfs->exists(fallback)) @@ -116,17 +112,17 @@ std::string Misc::ResourceHelpers::correctResourcePath(std::string_view topLevel std::string Misc::ResourceHelpers::correctTexturePath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath("textures", resPath, vfs, { "bookart" }); + return correctResourcePath({ "textures", "bookart" }, resPath, vfs); } std::string Misc::ResourceHelpers::correctIconPath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath("icons", resPath, vfs); + return correctResourcePath({ "icons" }, resPath, vfs); } std::string Misc::ResourceHelpers::correctBookartPath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath("bookart", resPath, vfs, { "textures" }); + return correctResourcePath({ "bookart", "textures" }, resPath, vfs); } std::string Misc::ResourceHelpers::correctBookartPath( diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index dc0b487a59..c98840dd61 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -23,8 +23,8 @@ namespace Misc namespace ResourceHelpers { bool changeExtensionToDds(std::string& path); - std::string correctResourcePath(std::string_view topLevelDirectory, std::string_view resPath, - const VFS::Manager* vfs, const std::vector& alternativeDirectories = {}); + std::string correctResourcePath(const std::vector& topLevelDirectories, + std::string_view resPath, const VFS::Manager* vfs); std::string correctTexturePath(std::string_view resPath, const VFS::Manager* vfs); std::string correctIconPath(std::string_view resPath, const VFS::Manager* vfs); std::string correctBookartPath(std::string_view resPath, const VFS::Manager* vfs); From 4d0aece001739f9d2fd72b2b0e54fbc55b66c8e7 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 11 Dec 2023 00:05:41 +0000 Subject: [PATCH 006/451] Clarify variable name --- components/misc/resourcehelpers.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index b602001913..7386dceb9f 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -67,10 +67,10 @@ std::string Misc::ResourceHelpers::correctResourcePath( // Handle top level directory bool needsPrefix = true; - for (std::string_view alternativeDirectory : topLevelDirectories) + for (std::string_view potentialTopLevelDirectory : topLevelDirectories) { - if (correctedPath.starts_with(alternativeDirectory) && correctedPath.size() > alternativeDirectory.size() - && correctedPath[alternativeDirectory.size()] == '\\') + if (correctedPath.starts_with(potentialTopLevelDirectory) && correctedPath.size() > potentialTopLevelDirectory.size() + && correctedPath[potentialTopLevelDirectory.size()] == '\\') { needsPrefix = false; break; From c2d1a4c861048727abea56458fce06e1b88bb7cf Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Jan 2024 01:18:15 +0000 Subject: [PATCH 007/451] Initial stab at OSG plugin checker It doesn't work yet due to osgDB::listAllAvailablePlugins returning a list of paths to dynamic libraries. That means: * the check fails when the required plugin is linked statically. * we're going to have to do something to slice up the filenames. * there'll probably be unicode errors when the OpenMW installation path isn't representable by the current eight-bit code page on Windows. Alternatively, we can switch to listing the required file extension support, and use osgDB::Registry::instance()->getReaderWriterList() and each element's supportedExtensions() function, but I don't think we've actually got that list of extensions anywhere and it might get desynced with the existing list of plugins if we add more. --- apps/openmw/main.cpp | 4 +++ components/CMakeLists.txt | 9 +++-- components/misc/osgpluginchecker.cpp.in | 47 +++++++++++++++++++++++++ components/misc/osgpluginchecker.hpp | 9 +++++ 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 components/misc/osgpluginchecker.cpp.in create mode 100644 components/misc/osgpluginchecker.hpp diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 5bbc0211c1..adf50bea0e 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -228,6 +229,9 @@ int runApplication(int argc, char* argv[]) if (parseOptions(argc, argv, *engine, cfgMgr)) { + if (!Misc::checkRequiredOSGPluginsArePresent()) + return 1; + engine->go(); } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f25a4cc621..ce4b431979 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -40,6 +40,11 @@ endif (GIT_CHECKOUT) list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") +# OSG plugin checker +set(OSG_PLUGIN_CHECKER_CPP_FILE "components/misc/osgpluginchecker.cpp") +configure_file("${OpenMW_SOURCE_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}") +list(APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}") + # source files add_component_dir (lua @@ -274,8 +279,8 @@ add_component_dir (esm4 add_component_dir (misc barrier budgetmeasurement color compression constants convert coordinateconverter display endianness float16 frameratelimiter - guarded math mathutil messageformatparser notnullptr objectpool osguservalues progressreporter resourcehelpers rng - strongtypedef thread timeconvert timer tuplehelpers tuplemeta utf8stream weakcache windows + guarded math mathutil messageformatparser notnullptr objectpool osgpluginchecker osguservalues progressreporter resourcehelpers + rng strongtypedef thread timeconvert timer tuplehelpers tuplemeta utf8stream weakcache windows ) add_component_dir (misc/strings diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in new file mode 100644 index 0000000000..56a7814d33 --- /dev/null +++ b/components/misc/osgpluginchecker.cpp.in @@ -0,0 +1,47 @@ +#include "components/misc/osgpluginchecker.hpp" + +#include + +#include + +#include +#include + +namespace Misc +{ + namespace + { + std::string_view USED_OSG_PLUGINS = "${USED_OSG_PLUGINS}"; + + constexpr std::vector getRequiredOSGPlugins() + { + std::vector requiredOSGPlugins; + auto currentStart = USED_OSG_PLUGINS.begin(); + while (currentStart != USED_OSG_PLUGINS.end()) + { + auto currentEnd = std::find(currentStart, USED_OSG_PLUGINS.end(), ';'); + requiredOSGPlugins.emplace_back(currentStart, currentEnd); + if (currentEnd == USED_OSG_PLUGINS.end()) + break; + currentStart = currentEnd + 1; + } + return requiredOSGPlugins; + } + } + + bool checkRequiredOSGPluginsArePresent() + { + auto availableOSGPlugins = osgDB::listAllAvailablePlugins(); + auto requiredOSGPlugins = getRequiredOSGPlugins(); + bool haveAllPlugins = true; + for (std::string_view plugin : requiredOSGPlugins) + { + if (std::find(availableOSGPlugins.begin(), availableOSGPlugins.end(), plugin) == availableOSGPlugins.end()) + { + Log(Debug::Error) << "Missing OSG plugin: " << plugin; + haveAllPlugins = false; + } + } + return haveAllPlugins; + } +} diff --git a/components/misc/osgpluginchecker.hpp b/components/misc/osgpluginchecker.hpp new file mode 100644 index 0000000000..2f5ea09700 --- /dev/null +++ b/components/misc/osgpluginchecker.hpp @@ -0,0 +1,9 @@ +#ifndef OPENMW_COMPONENTS_MISC_OSGPLUGINCHECKER_HPP +#define OPENMW_COMPONENTS_MISC_OSGPLUGINCHECKER_HPP + +namespace Misc +{ + bool checkRequiredOSGPluginsArePresent(); +} + +#endif From ef65f0c70d4f3659a2703003a89af236281a753e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Jan 2024 17:32:54 +0000 Subject: [PATCH 008/451] Make OSG plugin checker barely functional * Work out what module filenames should be in CMake, and give those to C++ * Compare just the module filenames instead of the full strings * Deal with OSG trying to support both UTF-8 and system-eight-bit-code-page file paths on Windows. * Add a comment complaining about the constexpr situation. * Use a stub implementation when using static OSG - apparently we don't actually support mixing and matching static and dynamic OSG plugins even though OSG itself does. --- components/CMakeLists.txt | 3 ++ components/misc/osgpluginchecker.cpp.in | 38 ++++++++++++++++++++----- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index ce4b431979..72a16c04fb 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -41,6 +41,9 @@ endif (GIT_CHECKOUT) list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") # OSG plugin checker +list(TRANSFORM USED_OSG_PLUGINS PREPEND "${CMAKE_SHARED_MODULE_PREFIX}" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES) +list(TRANSFORM USED_OSG_PLUGIN_FILENAMES APPEND "${CMAKE_SHARED_MODULE_SUFFIX}") + set(OSG_PLUGIN_CHECKER_CPP_FILE "components/misc/osgpluginchecker.cpp") configure_file("${OpenMW_SOURCE_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}") list(APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}") diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index 56a7814d33..5a7e5e92c7 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -1,27 +1,42 @@ #include "components/misc/osgpluginchecker.hpp" #include +#include +#include #include #include +#include #include namespace Misc { +#ifdef OSG_LIBRARY_STATIC + + bool checkRequiredOSGPluginsArePresent() + { + // assume they were linked in at build time and CMake would have failed if they were missing + return true; + } + +#else + namespace { - std::string_view USED_OSG_PLUGINS = "${USED_OSG_PLUGINS}"; + std::string_view USED_OSG_PLUGIN_FILENAMES = "${USED_OSG_PLUGIN_FILENAMES}"; - constexpr std::vector getRequiredOSGPlugins() + constexpr auto getRequiredOSGPlugins() { + // TODO: this is only constexpr due to an MSVC extension, so doesn't work on GCC and Clang + // I tried replacing it with std::views::split_range, but we support compilers without that bit of C++20, and it wasn't obvious how to use the result as a string_view without C++23 std::vector requiredOSGPlugins; - auto currentStart = USED_OSG_PLUGINS.begin(); - while (currentStart != USED_OSG_PLUGINS.end()) + auto currentStart = USED_OSG_PLUGIN_FILENAMES.begin(); + while (currentStart != USED_OSG_PLUGIN_FILENAMES.end()) { - auto currentEnd = std::find(currentStart, USED_OSG_PLUGINS.end(), ';'); + auto currentEnd = std::find(currentStart, USED_OSG_PLUGIN_FILENAMES.end(), ';'); requiredOSGPlugins.emplace_back(currentStart, currentEnd); - if (currentEnd == USED_OSG_PLUGINS.end()) + if (currentEnd == USED_OSG_PLUGIN_FILENAMES.end()) break; currentStart = currentEnd + 1; } @@ -36,7 +51,14 @@ namespace Misc bool haveAllPlugins = true; for (std::string_view plugin : requiredOSGPlugins) { - if (std::find(availableOSGPlugins.begin(), availableOSGPlugins.end(), plugin) == availableOSGPlugins.end()) + if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), [&](std::string availablePlugin) { +#ifdef OSG_USE_UTF8_FILENAME + std::filesystem::path pluginPath {stringToU8String(availablePlugin)}; +#else + std::filesystem::path pluginPath {availablePlugin}; +#endif + return pluginPath.filename() == plugin; + }) == availableOSGPlugins.end()) { Log(Debug::Error) << "Missing OSG plugin: " << plugin; haveAllPlugins = false; @@ -44,4 +66,6 @@ namespace Misc } return haveAllPlugins; } + +#endif } From de107c6a98abf9b84e6e4876a3dd34093041b690 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Jan 2024 17:35:52 +0000 Subject: [PATCH 009/451] Add missing _view --- components/misc/osgpluginchecker.cpp.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index 5a7e5e92c7..759b893d08 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -51,7 +51,7 @@ namespace Misc bool haveAllPlugins = true; for (std::string_view plugin : requiredOSGPlugins) { - if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), [&](std::string availablePlugin) { + if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), [&](std::string_view availablePlugin) { #ifdef OSG_USE_UTF8_FILENAME std::filesystem::path pluginPath {stringToU8String(availablePlugin)}; #else From 62f5c46f253f3fac78764b12a7bf8e360db8edf3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 12 Jan 2024 20:16:53 +0000 Subject: [PATCH 010/451] Split list in CMake instead of C++ That avoids the need for constexpr work, and therefore the need for an MSVC-specific extension --- components/CMakeLists.txt | 3 +++ components/misc/osgpluginchecker.cpp.in | 23 +++-------------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 72a16c04fb..536d1098c5 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -43,6 +43,9 @@ list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") # OSG plugin checker list(TRANSFORM USED_OSG_PLUGINS PREPEND "${CMAKE_SHARED_MODULE_PREFIX}" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES) list(TRANSFORM USED_OSG_PLUGIN_FILENAMES APPEND "${CMAKE_SHARED_MODULE_SUFFIX}") +list(TRANSFORM USED_OSG_PLUGIN_FILENAMES PREPEND "\"" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES_FORMATTED) +list(TRANSFORM USED_OSG_PLUGIN_FILENAMES_FORMATTED APPEND "\"") +list(JOIN USED_OSG_PLUGIN_FILENAMES_FORMATTED ", " USED_OSG_PLUGIN_FILENAMES_FORMATTED) set(OSG_PLUGIN_CHECKER_CPP_FILE "components/misc/osgpluginchecker.cpp") configure_file("${OpenMW_SOURCE_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}") diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index 759b893d08..9bc165c5d6 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -24,32 +25,14 @@ namespace Misc namespace { - std::string_view USED_OSG_PLUGIN_FILENAMES = "${USED_OSG_PLUGIN_FILENAMES}"; - - constexpr auto getRequiredOSGPlugins() - { - // TODO: this is only constexpr due to an MSVC extension, so doesn't work on GCC and Clang - // I tried replacing it with std::views::split_range, but we support compilers without that bit of C++20, and it wasn't obvious how to use the result as a string_view without C++23 - std::vector requiredOSGPlugins; - auto currentStart = USED_OSG_PLUGIN_FILENAMES.begin(); - while (currentStart != USED_OSG_PLUGIN_FILENAMES.end()) - { - auto currentEnd = std::find(currentStart, USED_OSG_PLUGIN_FILENAMES.end(), ';'); - requiredOSGPlugins.emplace_back(currentStart, currentEnd); - if (currentEnd == USED_OSG_PLUGIN_FILENAMES.end()) - break; - currentStart = currentEnd + 1; - } - return requiredOSGPlugins; - } + constexpr auto USED_OSG_PLUGIN_FILENAMES = std::to_array({${USED_OSG_PLUGIN_FILENAMES_FORMATTED}}); } bool checkRequiredOSGPluginsArePresent() { auto availableOSGPlugins = osgDB::listAllAvailablePlugins(); - auto requiredOSGPlugins = getRequiredOSGPlugins(); bool haveAllPlugins = true; - for (std::string_view plugin : requiredOSGPlugins) + for (std::string_view plugin : USED_OSG_PLUGIN_FILENAMES) { if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), [&](std::string_view availablePlugin) { #ifdef OSG_USE_UTF8_FILENAME From e0eb3feb89e6789bd16908d031ca275700e0be27 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 12 Jan 2024 23:49:53 +0000 Subject: [PATCH 011/451] Use OSG_PLUGIN_PREFIX instead of CMAKE_SHARED_MODULE_PREFIX Logic to generate it copied from OSG's CMake instead of guessed. --- components/CMakeLists.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 536d1098c5..a7f6d666d0 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -41,7 +41,14 @@ endif (GIT_CHECKOUT) list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") # OSG plugin checker -list(TRANSFORM USED_OSG_PLUGINS PREPEND "${CMAKE_SHARED_MODULE_PREFIX}" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES) +# Helpfully, OSG doesn't export this to its CMake config as it doesn't have one +set(OSG_PLUGIN_PREFIX "") +if (CYGWIN) + SET(OSG_PLUGIN_PREFIX "cygwin_") +elseif(MINGW) + SET(OSG_PLUGIN_PREFIX "mingw_") +endif() +list(TRANSFORM USED_OSG_PLUGINS PREPEND "${OSG_PLUGIN_PREFIX}" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES) list(TRANSFORM USED_OSG_PLUGIN_FILENAMES APPEND "${CMAKE_SHARED_MODULE_SUFFIX}") list(TRANSFORM USED_OSG_PLUGIN_FILENAMES PREPEND "\"" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES_FORMATTED) list(TRANSFORM USED_OSG_PLUGIN_FILENAMES_FORMATTED APPEND "\"") From 011d9d6493099065e6943b371cb7c7253a7a5e94 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 14 Jan 2024 20:33:23 +0100 Subject: [PATCH 012/451] Dehardcode skill and level progression --- apps/openmw/mwbase/luamanager.hpp | 1 + apps/openmw/mwclass/npc.cpp | 18 +- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 3 +- apps/openmw/mwgui/pickpocketitemmodel.cpp | 2 +- apps/openmw/mwlua/engineevents.cpp | 9 + apps/openmw/mwlua/engineevents.hpp | 15 +- apps/openmw/mwlua/localscripts.cpp | 2 +- apps/openmw/mwlua/localscripts.hpp | 5 + apps/openmw/mwlua/luamanagerimp.cpp | 5 + apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwlua/stats.cpp | 171 ++++++++++- apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwmechanics/alchemy.cpp | 2 +- apps/openmw/mwmechanics/character.cpp | 6 +- apps/openmw/mwmechanics/combat.cpp | 4 +- apps/openmw/mwmechanics/enchanting.cpp | 3 +- apps/openmw/mwmechanics/npcstats.cpp | 27 ++ apps/openmw/mwmechanics/npcstats.hpp | 4 + apps/openmw/mwmechanics/recharge.cpp | 2 +- apps/openmw/mwmechanics/repair.cpp | 2 +- apps/openmw/mwmechanics/security.cpp | 4 +- apps/openmw/mwmechanics/spellcasting.cpp | 6 +- apps/openmw/mwmechanics/trading.cpp | 3 +- apps/openmw/mwphysics/physicssystem.cpp | 2 +- apps/openmw/mwworld/actioneat.cpp | 2 +- components/esm3/loadskil.hpp | 34 ++- docs/source/luadoc_data_paths.sh | 1 + docs/source/reference/lua-scripting/api.rst | 1 + .../interface_skill_progression.rst | 6 + .../lua-scripting/tables/interfaces.rst | 4 + files/data/CMakeLists.txt | 1 + files/data/builtin.omwscripts | 1 + .../omw/mechanics/playercontroller.lua | 75 +++++ files/data/scripts/omw/skillhandlers.lua | 283 ++++++++++++++++++ files/lua_api/openmw/core.lua | 4 + files/lua_api/openmw/interfaces.lua | 3 + files/lua_api/openmw/types.lua | 21 +- 37 files changed, 687 insertions(+), 48 deletions(-) create mode 100644 docs/source/reference/lua-scripting/interface_skill_progression.rst create mode 100644 files/data/scripts/omw/skillhandlers.lua diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 57886d5399..115416cd8c 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -62,6 +62,7 @@ namespace MWBase virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) = 0; virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0; + virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0; virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index de587954b8..7eda9cd2cf 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -22,6 +22,7 @@ #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" @@ -655,7 +656,7 @@ namespace MWClass ESM::RefId weapskill = ESM::Skill::HandToHand; if (!weapon.isEmpty()) weapskill = weapon.getClass().getEquipmentSkill(weapon); - skillUsageSucceeded(ptr, weapskill, 0); + skillUsageSucceeded(ptr, weapskill, ESM::Skill::Weapon_SuccessfulHit); const MWMechanics::AiSequence& seq = victim.getClass().getCreatureStats(victim).getAiSequence(); @@ -845,7 +846,7 @@ namespace MWClass ESM::RefId skill = armor.getClass().getEquipmentSkill(armor); if (ptr == MWMechanics::getPlayer()) - skillUsageSucceeded(ptr, skill, 0); + skillUsageSucceeded(ptr, skill, ESM::Skill::Armor_HitByOpponent); if (skill == ESM::Skill::LightArmor) sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Light Armor Hit"), 1.0f, 1.0f); @@ -855,7 +856,7 @@ namespace MWClass sndMgr->playSound3D(ptr, ESM::RefId::stringRefId("Heavy Armor Hit"), 1.0f, 1.0f); } else if (ptr == MWMechanics::getPlayer()) - skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0); + skillUsageSucceeded(ptr, ESM::Skill::Unarmored, ESM::Skill::Armor_HitByOpponent); } } @@ -1131,16 +1132,7 @@ namespace MWClass void Npc::skillUsageSucceeded(const MWWorld::Ptr& ptr, ESM::RefId skill, int usageType, float extraFactor) const { - MWMechanics::NpcStats& stats = getNpcStats(ptr); - - if (stats.isWerewolf()) - return; - - MWWorld::LiveCellRef* ref = ptr.get(); - - const ESM::Class* class_ = MWBase::Environment::get().getESMStore()->get().find(ref->mBase->mClass); - - stats.useSkill(skill, *class_, usageType, extraFactor); + MWBase::Environment::get().getLuaManager()->skillUse(ptr, skill, usageType, extraFactor); } float Npc::getArmorRating(const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 2b7774d8c9..3b0ba47250 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -543,7 +543,8 @@ namespace MWDialogue mPermanentDispositionChange += perm; MWWorld::Ptr player = MWMechanics::getPlayer(); - player.getClass().skillUsageSucceeded(player, ESM::Skill::Speechcraft, success ? 0 : 1); + player.getClass().skillUsageSucceeded( + player, ESM::Skill::Speechcraft, success ? ESM::Skill::Speechcraft_Success : ESM::Skill::Speechcraft_Fail); if (success) { diff --git a/apps/openmw/mwgui/pickpocketitemmodel.cpp b/apps/openmw/mwgui/pickpocketitemmodel.cpp index bfaf011835..fa7bce449b 100644 --- a/apps/openmw/mwgui/pickpocketitemmodel.cpp +++ b/apps/openmw/mwgui/pickpocketitemmodel.cpp @@ -134,7 +134,7 @@ namespace MWGui return false; } else - player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 1); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, ESM::Skill::Sneak_PickPocket); return true; } diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 43507ff1a5..7cc2b3db48 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -94,6 +94,15 @@ namespace MWLua if (auto* scripts = getLocalScripts(actor)) scripts->onAnimationTextKey(event.mGroupname, event.mKey); } + + void operator()(const OnSkillUse& event) const + { + MWWorld::Ptr actor = getPtr(event.mActor); + if (actor.isEmpty()) + return; + if (auto* scripts = getLocalScripts(actor)) + scripts->onSkillUse(event.mSkill, event.useType, event.scale); + } private: MWWorld::Ptr getPtr(ESM::RefNum id) const diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index bf8d219fd5..b9f4c25b39 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -57,8 +57,21 @@ namespace MWLua std::string mGroupname; std::string mKey; }; + struct OnAnimationTextKey + { + ESM::RefNum mActor; + std::string mGroupname; + std::string mKey; + }; + struct OnSkillUse + { + ESM::RefNum mActor; + std::string mSkill; + int useType; + float scale; + }; using Event = std::variant; + OnAnimationTextKey, OnSkillUse>; void clear() { mQueue.clear(); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); } diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 1d5e710869..0c0af5b902 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -176,7 +176,7 @@ namespace MWLua { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, - &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers }); + &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse }); } void LocalScripts::setActive(bool active) diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index 230ec93d3c..f5f740b1c6 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -79,6 +79,10 @@ namespace MWLua { callEngineHandlers(mOnPlayAnimationHandlers, groupname, options); } + void onSkillUse(std::string_view skillId, int useType, float scale) + { + callEngineHandlers(mOnSkillUse, skillId, useType, scale); + } void applyStatsCache(); @@ -93,6 +97,7 @@ namespace MWLua EngineHandlerList mOnTeleportedHandlers{ "onTeleported" }; EngineHandlerList mOnAnimationTextKeyHandlers{ "_onAnimationTextKey" }; EngineHandlerList mOnPlayAnimationHandlers{ "_onPlayAnimation" }; + EngineHandlerList mOnSkillUse{ "_onSkillUse" }; }; } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index ac5ae60f7d..0974856d25 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -406,6 +406,11 @@ namespace MWLua scripts->onPlayAnimation(groupname, options); } + void LuaManager::skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) + { + mEngineEvents.addToQueue(EngineEvents::OnSkillUse{ getId(actor), skillId.serializeText(), useType, scale }); + } + void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) { mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index a539d04348..2e29ff272a 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -84,6 +84,7 @@ namespace MWLua const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) override; + void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) override; void exteriorCreated(MWWorld::CellStore& cell) override { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 02bed00bf5..224fdba0f2 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -73,6 +73,94 @@ namespace MWLua "StatUpdateAction"); } + static void setCreatureValue(Index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto& stats = ptr.getClass().getCreatureStats(ptr); + if (prop == "current") + stats.setLevel(LuaUtil::cast(value)); + } + + static void setNpcValue(Index index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto& stats = ptr.getClass().getNpcStats(ptr); + if (prop == "progress") + stats.setLevelProgress(LuaUtil::cast(value)); + else if (prop == "skillIncreasesForAttribute") + stats.setSkillIncreasesForAttribute( + *std::get(index).getIf(), LuaUtil::cast(value)); + else if (prop == "skillIncreasesForSpecialization") + stats.setSkillIncreasesForSpecialization(std::get(index), LuaUtil::cast(value)); + } + + class SkillIncreasesForAttributeStats + { + ObjectVariant mObject; + + public: + SkillIncreasesForAttributeStats(ObjectVariant object) + : mObject(std::move(object)) + { + } + + sol::object get(const Context& context, ESM::StringRefId attributeId) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return sol::nil; + + return getValue(context, mObject, &setNpcValue, attributeId, "skillIncreasesForAttribute", + [attributeId](const MWWorld::Ptr& ptr) { + return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForAttribute(attributeId); + }); + } + + void set(const Context& context, ESM::StringRefId attributeId, const sol::object& value) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return; + + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, attributeId, "skillIncreasesForAttribute" }] = value; + } + }; + + class SkillIncreasesForSpecializationStats + { + ObjectVariant mObject; + + public: + SkillIncreasesForSpecializationStats(ObjectVariant object) + : mObject(std::move(object)) + { + } + + sol::object get(const Context& context, int specialization) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return sol::nil; + + return getValue(context, mObject, &setNpcValue, specialization, "skillIncreasesForSpecialization", + [specialization](const MWWorld::Ptr& ptr) { + return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForSpecialization(specialization); + }); + } + + void set(const Context& context, int specialization, const sol::object& value) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return; + + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, specialization, "skillIncreasesForSpecialization" }] + = value; + } + }; + class LevelStat { ObjectVariant mObject; @@ -85,7 +173,7 @@ namespace MWLua public: sol::object getCurrent(const Context& context) const { - return getValue(context, mObject, &LevelStat::setValue, std::monostate{}, "current", + return getValue(context, mObject, &setCreatureValue, std::monostate{}, "current", [](const MWWorld::Ptr& ptr) { return ptr.getClass().getCreatureStats(ptr).getLevel(); }); } @@ -93,7 +181,7 @@ namespace MWLua { SelfObject* obj = mObject.asSelfObject(); addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &LevelStat::setValue, std::monostate{}, "current" }] = value; + obj->mStatsCache[SelfObject::CachedStat{ &setCreatureValue, std::monostate{}, "current" }] = value; } sol::object getProgress(const Context& context) const @@ -101,7 +189,30 @@ namespace MWLua const auto& ptr = mObject.ptr(); if (!ptr.getClass().isNpc()) return sol::nil; - return sol::make_object(context.mLua->sol(), ptr.getClass().getNpcStats(ptr).getLevelProgress()); + + return getValue(context, mObject, &setNpcValue, std::monostate{}, "progress", + [](const MWWorld::Ptr& ptr) { return ptr.getClass().getNpcStats(ptr).getLevelProgress(); }); + } + + void setProgress(const Context& context, const sol::object& value) const + { + const auto& ptr = mObject.ptr(); + if (!ptr.getClass().isNpc()) + return; + + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &setNpcValue, std::monostate{}, "progress" }] = value; + } + + SkillIncreasesForAttributeStats getSkillIncreasesForAttributeStats() const + { + return SkillIncreasesForAttributeStats{ mObject }; + } + + SkillIncreasesForSpecializationStats getSkillIncreasesForSpecializationStats() const + { + return SkillIncreasesForSpecializationStats{ mObject }; } static std::optional create(ObjectVariant object, Index) @@ -110,13 +221,6 @@ namespace MWLua return {}; return LevelStat{ std::move(object) }; } - - static void setValue(Index, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) - { - auto& stats = ptr.getClass().getCreatureStats(ptr); - if (prop == "current") - stats.setLevel(LuaUtil::cast(value)); - } }; class DynamicStat @@ -323,6 +427,14 @@ namespace MWLua namespace sol { + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; template <> struct is_automagical : std::false_type { @@ -360,10 +472,39 @@ namespace MWLua sol::table stats(context.mLua->sol(), sol::create); actor["stats"] = LuaUtil::makeReadOnly(stats); + auto skillIncreasesForAttributeStatsT + = context.mLua->sol().new_usertype("SkillIncreasesForAttributeStats"); + for (auto attribute : MWBase::Environment::get().getESMStore()->get()) + { + skillIncreasesForAttributeStatsT[ESM::RefId(attribute.mId).serializeText()] = sol::property( + [=](const SkillIncreasesForAttributeStats& stat) { return stat.get(context, attribute.mId); }, + [=](const SkillIncreasesForAttributeStats& stat, const sol::object& value) { + stat.set(context, attribute.mId, value); + }); + } + // ESM::Class::specializationIndexToLuaId.at(rec.mData.mSpecialization) + auto skillIncreasesForSpecializationStatsT + = context.mLua->sol().new_usertype( + "skillIncreasesForSpecializationStats"); + for (int i = 0; i < 3; i++) + { + std::string_view index = ESM::Class::specializationIndexToLuaId.at(i); + skillIncreasesForSpecializationStatsT[index] + = sol::property([=](const SkillIncreasesForSpecializationStats& stat) { return stat.get(context, i); }, + [=](const SkillIncreasesForSpecializationStats& stat, const sol::object& value) { + stat.set(context, i, value); + }); + } + auto levelStatT = context.mLua->sol().new_usertype("LevelStat"); levelStatT["current"] = sol::property([context](const LevelStat& stat) { return stat.getCurrent(context); }, [context](const LevelStat& stat, const sol::object& value) { stat.setCurrent(context, value); }); - levelStatT["progress"] = sol::property([context](const LevelStat& stat) { return stat.getProgress(context); }); + levelStatT["progress"] = sol::property([context](const LevelStat& stat) { return stat.getProgress(context); }, + [context](const LevelStat& stat, const sol::object& value) { stat.setProgress(context, value); }); + levelStatT["skillIncreasesForAttribute"] + = sol::readonly_property([](const LevelStat& stat) { return stat.getSkillIncreasesForAttributeStats(); }); + levelStatT["skillIncreasesForSpecialization"] = sol::readonly_property( + [](const LevelStat& stat) { return stat.getSkillIncreasesForSpecializationStats(); }); stats["level"] = addIndexedAccessor(0); auto dynamicStatT = context.mLua->sol().new_usertype("DynamicStat"); @@ -461,6 +602,14 @@ namespace MWLua skillT["attribute"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string { return ESM::Attribute::indexToRefId(rec.mData.mAttribute).serializeText(); }); + skillT["skillGain1"] + = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[0]; }); + skillT["skillGain2"] + = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[1]; }); + skillT["skillGain3"] + = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[2]; }); + skillT["skillGain4"] + = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[3]; }); auto schoolT = context.mLua->sol().new_usertype("MagicSchool"); schoolT[sol::meta_function::to_string] diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index a4e7168b0f..1f8265aab5 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1962,7 +1962,7 @@ namespace MWMechanics mSneakSkillTimer = 0.f; if (avoidedNotice && mSneakSkillTimer == 0.f) - player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, 0); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Sneak, ESM::Skill::Sneak_AvoidNotice); if (!detected) MWBase::Environment::get().getWindowManager()->setSneakVisibility(true); diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index aea3e36632..5a995e7f06 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -335,7 +335,7 @@ void MWMechanics::Alchemy::addPotion(const std::string& name) void MWMechanics::Alchemy::increaseSkill() { - mAlchemist.getClass().skillUsageSucceeded(mAlchemist, ESM::Skill::Alchemy, 0); + mAlchemist.getClass().skillUsageSucceeded(mAlchemist, ESM::Skill::Alchemy, ESM::Skill::Alchemy_CreatePotion); } float MWMechanics::Alchemy::getAlchemyFactor() const diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 8d69da2c43..7330b72255 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2088,7 +2088,7 @@ namespace MWMechanics mSecondsOfSwimming += duration; while (mSecondsOfSwimming > 1) { - cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 1); + cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, ESM::Skill::Athletics_SwimOneSecond); mSecondsOfSwimming -= 1; } } @@ -2097,7 +2097,7 @@ namespace MWMechanics mSecondsOfRunning += duration; while (mSecondsOfRunning > 1) { - cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, 0); + cls.skillUsageSucceeded(mPtr, ESM::Skill::Athletics, ESM::Skill::Athletics_RunOneSecond); mSecondsOfRunning -= 1; } } @@ -2215,7 +2215,7 @@ namespace MWMechanics { // report acrobatics progression if (isPlayer) - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, ESM::Skill::Acrobatics_Fall); } } diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 3208ea2293..06a1c3b9cc 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -167,7 +167,7 @@ namespace MWMechanics blockerStats.setBlock(true); if (blocker == getPlayer()) - blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, 0); + blocker.getClass().skillUsageSucceeded(blocker, ESM::Skill::Block, ESM::Skill::Block_Success); return true; } @@ -267,7 +267,7 @@ namespace MWMechanics applyWerewolfDamageMult(victim, projectile, damage); if (attacker == getPlayer()) - attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, 0); + attacker.getClass().skillUsageSucceeded(attacker, weaponSkill, ESM::Skill::Weapon_SuccessfulHit); const MWMechanics::AiSequence& sequence = victim.getClass().getCreatureStats(victim).getAiSequence(); bool unaware = attacker == getPlayer() && !sequence.isInCombat() diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 13894146b3..1de55b0c8d 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -84,7 +84,8 @@ namespace MWMechanics if (getEnchantChance() <= (Misc::Rng::roll0to99(prng))) return false; - mEnchanter.getClass().skillUsageSucceeded(mEnchanter, ESM::Skill::Enchant, 2); + mEnchanter.getClass().skillUsageSucceeded( + mEnchanter, ESM::Skill::Enchant, ESM::Skill::Enchant_CreateMagicItem); } enchantment.mEffects = mEffectList; diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 808059fccd..046c94fc8f 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -298,6 +298,11 @@ int MWMechanics::NpcStats::getLevelProgress() const return mLevelProgress; } +void MWMechanics::NpcStats::setLevelProgress(int progress) +{ + mLevelProgress = progress; +} + void MWMechanics::NpcStats::levelUp() { const MWWorld::Store& gmst = MWBase::Environment::get().getESMStore()->get(); @@ -344,11 +349,33 @@ int MWMechanics::NpcStats::getLevelupAttributeMultiplier(ESM::Attribute::Attribu return MWBase::Environment::get().getESMStore()->get().find(gmst.str())->mValue.getInteger(); } +int MWMechanics::NpcStats::getSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute) const +{ + auto it = mSkillIncreases.find(attribute); + if (it == mSkillIncreases.end()) + return 0; + return it->second; +} + +void MWMechanics::NpcStats::setSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute, int increases) +{ + if (increases == 0) + mSkillIncreases.erase(attribute); + else + mSkillIncreases[attribute] = increases; +} + int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(int spec) const { return mSpecIncreases[spec]; } +void MWMechanics::NpcStats::setSkillIncreasesForSpecialization(int spec, int increases) +{ + assert(spec >= 0 && spec < 3); + mSpecIncreases[spec] = increases; +} + void MWMechanics::NpcStats::flagAsUsed(const ESM::RefId& id) { mUsedIds.insert(id); diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 7113ee6207..9695d45ac4 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -92,10 +92,14 @@ namespace MWMechanics void increaseSkill(ESM::RefId id, const ESM::Class& class_, bool preserveProgress, bool readBook = false); int getLevelProgress() const; + void setLevelProgress(int progress); int getLevelupAttributeMultiplier(ESM::Attribute::AttributeID attribute) const; + int getSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute) const; + void setSkillIncreasesForAttribute(ESM::Attribute::AttributeID, int increases); int getSkillIncreasesForSpecialization(int spec) const; + void setSkillIncreasesForSpecialization(int spec, int increases); void levelUp(); diff --git a/apps/openmw/mwmechanics/recharge.cpp b/apps/openmw/mwmechanics/recharge.cpp index 6e16436bcc..7b0ad75d3c 100644 --- a/apps/openmw/mwmechanics/recharge.cpp +++ b/apps/openmw/mwmechanics/recharge.cpp @@ -84,7 +84,7 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Enchant Fail")); } - player.getClass().skillUsageSucceeded(player, ESM::Skill::Enchant, 0); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Enchant, ESM::Skill::Enchant_Recharge); gem.getContainerStore()->remove(gem, 1); if (gem.getCellRef().getCount() == 0) diff --git a/apps/openmw/mwmechanics/repair.cpp b/apps/openmw/mwmechanics/repair.cpp index 3011004244..914fa0b542 100644 --- a/apps/openmw/mwmechanics/repair.cpp +++ b/apps/openmw/mwmechanics/repair.cpp @@ -70,7 +70,7 @@ namespace MWMechanics stacked->getRefData().getLocals().setVarByInt(script, "onpcrepair", 1); // increase skill - player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, 0); + player.getClass().skillUsageSucceeded(player, ESM::Skill::Armorer, ESM::Skill::Armorer_Repair); MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Repair")); MWBase::Environment::get().getWindowManager()->messageBox("#{sRepairSuccess}"); diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index a13131cae6..0fb8a95699 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -64,7 +64,7 @@ namespace MWMechanics lock.getCellRef().unlock(); resultMessage = "#{sLockSuccess}"; resultSound = "Open Lock"; - mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 1); + mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, ESM::Skill::Security_PickLock); } else resultMessage = "#{sLockFail}"; @@ -115,7 +115,7 @@ namespace MWMechanics resultSound = "Disarm Trap"; resultMessage = "#{sTrapSuccess}"; - mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, 0); + mActor.getClass().skillUsageSucceeded(mActor, ESM::Skill::Security, ESM::Skill::Security_DisarmTrap); } else resultMessage = "#{sTrapFail}"; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 0496033c70..721131dcb0 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -354,7 +354,7 @@ namespace MWMechanics if (type == ESM::Enchantment::WhenUsed) { if (mCaster == getPlayer()) - mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, 1); + mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, ESM::Skill::Enchant_UseMagicItem); } else if (type == ESM::Enchantment::CastOnce) { @@ -364,7 +364,7 @@ namespace MWMechanics else if (type == ESM::Enchantment::WhenStrikes) { if (mCaster == getPlayer()) - mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, 3); + mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, ESM::Skill::Enchant_CastOnStrike); } if (isProjectile) @@ -439,7 +439,7 @@ namespace MWMechanics } if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell)) - mCaster.getClass().skillUsageSucceeded(mCaster, school, 0); + mCaster.getClass().skillUsageSucceeded(mCaster, school, ESM::Skill::Spellcast_Success); // A non-actor doesn't play its spell cast effects from a character controller, so play them here if (!mCaster.getClass().isActor()) diff --git a/apps/openmw/mwmechanics/trading.cpp b/apps/openmw/mwmechanics/trading.cpp index 9500897f25..b7e361c0b9 100644 --- a/apps/openmw/mwmechanics/trading.cpp +++ b/apps/openmw/mwmechanics/trading.cpp @@ -79,7 +79,8 @@ namespace MWMechanics { skillGain = std::floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); } - player.getClass().skillUsageSucceeded(player, ESM::Skill::Mercantile, 0, skillGain); + player.getClass().skillUsageSucceeded( + player, ESM::Skill::Mercantile, ESM::Skill::Mercantile_Success, skillGain); return true; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 149113dfb1..bd2d43dd5a 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -69,7 +69,7 @@ namespace // Advance acrobatics and set flag for GetPCJumping if (isPlayer) { - ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0); + ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, ESM::Skill::Acrobatics_Jump); MWBase::Environment::get().getWorld()->getPlayer().setJumping(true); } diff --git a/apps/openmw/mwworld/actioneat.cpp b/apps/openmw/mwworld/actioneat.cpp index b77fe146ef..c67502bdee 100644 --- a/apps/openmw/mwworld/actioneat.cpp +++ b/apps/openmw/mwworld/actioneat.cpp @@ -11,7 +11,7 @@ namespace MWWorld void ActionEat::executeImp(const Ptr& actor) { if (actor.getClass().consume(getTarget(), actor) && actor == MWMechanics::getPlayer()) - actor.getClass().skillUsageSucceeded(actor, ESM::Skill::Alchemy, 1); + actor.getClass().skillUsageSucceeded(actor, ESM::Skill::Alchemy, ESM::Skill::Alchemy_UseIngredient); } ActionEat::ActionEat(const MWWorld::Ptr& object) diff --git a/components/esm3/loadskil.hpp b/components/esm3/loadskil.hpp index 978e3d5dc4..9cae87903c 100644 --- a/components/esm3/loadskil.hpp +++ b/components/esm3/loadskil.hpp @@ -47,13 +47,45 @@ namespace ESM uint32_t mRecordFlags; SkillId mId; + //! Enum that defines the index into SKDTstruct::mUseValue for all vanilla skill uses + enum UseType + { + // These are shared by multiple skills + Armor_HitByOpponent = 0, + Block_Success = 0, + Spellcast_Success = 0, + Weapon_SuccessfulHit = 0, + + // Skill-specific use types + Alchemy_CreatePotion = 0, + Alchemy_UseIngredient = 1, + Enchant_Recharge = 0, + Enchant_UseMagicItem = 1, + Enchant_CreateMagicItem = 2, + Enchant_CastOnStrike = 3, + Acrobatics_Jump = 0, + Acrobatics_Fall = 1, + Mercantile_Success = 0, + Mercantile_Bribe = 1, //!< \Note This is bugged in vanilla and is not actually in use. + Security_DisarmTrap = 0, + Security_PickLock = 1, + Sneak_AvoidNotice = 0, + Sneak_PickPocket = 1, + Speechcraft_Success = 0, + Speechcraft_Fail = 1, + Armorer_Repair = 0, + Athletics_RunOneSecond = 0, + Athletics_SwimOneSecond = 0, + + }; + struct SKDTstruct { int32_t mAttribute; // see defs.hpp int32_t mSpecialization; // 0 - Combat, 1 - Magic, 2 - Stealth float mUseValue[4]; // How much skill improves through use. Meaning // of each field depends on what skill this - // is. We should document this better later. + // is. See UseType above }; // Total size: 24 bytes SKDTstruct mData; diff --git a/docs/source/luadoc_data_paths.sh b/docs/source/luadoc_data_paths.sh index 02b03cbd69..a9f7c71067 100755 --- a/docs/source/luadoc_data_paths.sh +++ b/docs/source/luadoc_data_paths.sh @@ -9,5 +9,6 @@ paths=( scripts/omw/settings/player.lua scripts/omw/ui.lua scripts/omw/usehandlers.lua + scripts/omw/skillhandlers.lua ) printf '%s\n' "${paths[@]}" \ No newline at end of file diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 1bb7e0b6e9..2f4708810b 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -40,6 +40,7 @@ Lua API reference interface_item_usage interface_mwui interface_settings + interface_skill_progression interface_ui iterables diff --git a/docs/source/reference/lua-scripting/interface_skill_progression.rst b/docs/source/reference/lua-scripting/interface_skill_progression.rst new file mode 100644 index 0000000000..5cd2e87812 --- /dev/null +++ b/docs/source/reference/lua-scripting/interface_skill_progression.rst @@ -0,0 +1,6 @@ +Interface SkillProgression +=================== + +.. raw:: html + :file: generated_html/scripts_omw_skillhandlers.html + diff --git a/docs/source/reference/lua-scripting/tables/interfaces.rst b/docs/source/reference/lua-scripting/tables/interfaces.rst index 5029baf0a3..0692e60b1c 100644 --- a/docs/source/reference/lua-scripting/tables/interfaces.rst +++ b/docs/source/reference/lua-scripting/tables/interfaces.rst @@ -25,6 +25,10 @@ - by global scripts - | Allows to extend or override built-in item usage | mechanics. + * - :ref:`SkillProgression ` + - by local scripts + - | Control, extend, and override skill progression of the + - | player. * - :ref:`Settings ` - by player and global scripts - Save, display and track changes of setting values. diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index 3ab30c87ff..a0c97ad308 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -90,6 +90,7 @@ set(BUILTIN_DATA_FILES scripts/omw/mwui/textEdit.lua scripts/omw/mwui/space.lua scripts/omw/mwui/init.lua + scripts/omw/skillhandlers.lua scripts/omw/ui.lua scripts/omw/usehandlers.lua scripts/omw/worldeventhandlers.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index a6f4ca5f33..a07b62c5fb 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -16,6 +16,7 @@ PLAYER: scripts/omw/playercontrols.lua PLAYER: scripts/omw/camera/camera.lua PLAYER: scripts/omw/input/actionbindings.lua PLAYER: scripts/omw/input/smoothmovement.lua +PLAYER: scripts/omw/skillhandlers.lua NPC,CREATURE: scripts/omw/ai.lua # User interface diff --git a/files/data/scripts/omw/mechanics/playercontroller.lua b/files/data/scripts/omw/mechanics/playercontroller.lua index 19e62d02c7..29baf32020 100644 --- a/files/data/scripts/omw/mechanics/playercontroller.lua +++ b/files/data/scripts/omw/mechanics/playercontroller.lua @@ -1,7 +1,13 @@ +local ambient = require('openmw.ambient') local core = require('openmw.core') +local Skill = core.stats.Skill +local I = require('openmw.interfaces') local nearby = require('openmw.nearby') local self = require('openmw.self') local types = require('openmw.types') +local NPC = types.NPC +local Actor = types.Actor +local ui = require('openmw.ui') local cell = nil local autodoors = {} @@ -31,6 +37,69 @@ local function processAutomaticDoors() end end +local function skillLevelUpHandler(skillid, source, params) + local skillStat = NPC.stats.skills[skillid](self) + if skillStat.base >= 100 then + return false + end + + if params.skillIncreaseValue then + skillStat.base = skillStat.base + params.skillIncreaseValue + end + + local levelStat = Actor.stats.level(self) + if params.levelUpProgress then + levelStat.progress = levelStat.progress + params.levelUpProgress + end + + if params.levelUpAttribute and params.levelUpAttributeIncreaseValue then + levelStat.skillIncreasesForAttribute[params.levelUpAttribute] + = levelStat.skillIncreasesForAttribute[params.levelUpAttribute] + params.levelUpAttributeIncreaseValue + end + + if params.levelUpSpecialization and params.levelUpSpecializationIncreaseValue then + levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] + = levelStat.skillIncreasesForSpecialization[params.levelUpSpecialization] + params.levelUpSpecializationIncreaseValue; + end + + local skillRecord = Skill.record(skillid) + local npcRecord = NPC.record(self) + local class = NPC.classes.record(npcRecord.class) + + ambient.playSound("skillraise") + + local message = core.getGMST('sNotifyMessage39') + message = message:gsub("%%s", skillRecord.name) + message = message:gsub("%%d", tostring(skillStat.base)) + + if source == I.SkillProgression.SKILL_INCREASE_SOURCES.Book then + message = '#{sBookSkillMessage}\n'..message + end + + ui.showMessage(message) + + if levelStat.progress >= core.getGMST('iLevelUpTotal') then + ui.showMessage('#{sLevelUpMsg}') + end + + if not source or source == I.SkillProgression.SKILL_INCREASE_SOURCES.Usage then skillStat.progress = 0 end +end + +local function skillUsedHandler(skillid, useType, params) + if NPC.isWerewolf(self) then + return false + end + + if params.skillGain then + local skillStat = NPC.stats.skills[skillid](self) + skillStat.progress = skillStat.progress + params.skillGain + + if skillStat.progress >= 1 then + I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Usage) + end + end +end + local function onUpdate() if self.cell ~= cell then cell = self.cell @@ -39,8 +108,14 @@ local function onUpdate() processAutomaticDoors() end +local function onActive() + I.SkillProgression.addSkillUsedHandler(skillUsedHandler) + I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler) +end + return { engineHandlers = { onUpdate = onUpdate, + onActive = onActive, }, } diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua new file mode 100644 index 0000000000..a4fb42ebde --- /dev/null +++ b/files/data/scripts/omw/skillhandlers.lua @@ -0,0 +1,283 @@ +local self = require('openmw.self') +local types = require('openmw.types') +local core = require('openmw.core') +local NPC = require('openmw.types').NPC +local Skill = core.stats.Skill + +--- +-- Table of skill use types defined by morrowind. +-- Each entry corresponds to an index into the available skill gain values +-- of a @{openmw.types#SkillRecord} +-- @type SkillUseType +-- @field #number Armor_HitByOpponent 0 +-- @field #number Block_Success 0 +-- @field #number Spellcast_Success 0 +-- @field #number Weapon_SuccessfulHit 0 +-- @field #number Alchemy_CreatePotion 0 +-- @field #number Alchemy_UseIngredient 1 +-- @field #number Enchant_Recharge 0 +-- @field #number Enchant_UseMagicItem 1 +-- @field #number Enchant_CreateMagicItem 2 +-- @field #number Enchant_CastOnStrike 3 +-- @field #number Acrobatics_Jump 0 +-- @field #number Acrobatics_Fall 1 +-- @field #number Mercantile_Success 0 +-- @field #number Mercantile_Bribe 1 +-- @field #number Security_DisarmTrap 0 +-- @field #number Security_PickLock 1 +-- @field #number Sneak_AvoidNotice 0 +-- @field #number Sneak_PickPocket 1 +-- @field #number Speechcraft_Success 0 +-- @field #number Speechcraft_Fail 1 +-- @field #number Armorer_Repair 0 +-- @field #number Athletics_RunOneSecond 0 +-- @field #number Athletics_SwimOneSecond 0 + +--- +-- Table of valid sources for skill increases +-- @type SkillLevelUpSource +-- @field #string Book book +-- @field #string Trainer trainer +-- @field #string Usage usage + +--- +-- Table of valid handler signatures +-- @type HandlerSignatures +-- + +--- Signature of the skillLevelUp handler +-- @function [parent=#HandlerSignatures] skillLevelUpHandler +-- @param #string skillid The ID of the skill being leveled up +-- @param #SkillLevelUpSource source The source of the skill level up +-- @param #table params Modifiable skill level up values as a table. These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values: +-- +-- * `skillIncreaseValue` - The numeric amount of skill levels gained. +-- * `levelUpProgress` - The numeric amount of level up progress gained. +-- * `levelUpAttribute` - The string identifying the attribute that should receive points from this skill level up. +-- * `levelUpAttributeIncreaseValue` - The numeric amount of attribute increase points received. This contributes to the amount of each attribute the character receives during a vanilla level up. +-- * `levelUpSpecialization` - The string identifying the specialization that should receive points from this skill level up. +-- * `levelUpSpecializationIncreaseValue` - The numeric amount of specialization increase points received. This contributes to the icon displayed at the level up screen during a vanilla level up. + +--- Signature of the skillUsed handler +-- @function [parent=#HandlerSignatures] skillUsedHandler +-- @param #string skillid The ID of the skill being progressed +-- @param #SkillUseType useType The use type the skill progression +-- @param #table params Modifiable skill progression value. By default contains the single value +-- +-- * `skillGain` - The numeric amount of skill progress gained, normalized to the range 0 to 1, where 1 is a full level. + + +local skillUsedHandlers = {} +local skillLevelUpHandlers = {} + +local function tableHasValue(table, value) + for _, v in pairs(table) do + if v == value then return true end + end + return false +end + +local function getSkillGainValue(skillid, useType, scale) + local skillRecord = Skill.record(skillid) + + local skillGain = 0 + if useType == 0 then + skillGain = skillRecord.skillGain1 + elseif useType == 1 then + skillGain = skillRecord.skillGain2 + elseif useType == 2 then + skillGain = skillRecord.skillGain3 + elseif useType == 3 then + skillGain = skillRecord.skillGain4 + end + + if scale ~= nil then skillGain = skillGain * scale end + return skillGain +end + +local function getSkillProgressRequirementUnorm(npc, skillid) + local npcRecord = NPC.record(npc) + local class = NPC.classes.record(npcRecord.class) + local skillStat = NPC.stats.skills[skillid](npc) + local skillRecord = Skill.record(skillid) + + local factor = core.getGMST('fMiscSkillBonus') + if tableHasValue(class.majorSkills, skillid) then + factor = core.getGMST('fMajorSkillBonus') + elseif tableHasValue(class.minorSkills, skillid) then + factor = core.getGMST('fMinorSkillBonus') + end + + if skillRecord.specialization == class.specialization then + factor = factor * core.getGMST('fSpecialSkillBonus') + end + + return (skillStat.base + 1) * factor +end + +local function skillUsed(skillid, useType, scale) + if #skillUsedHandlers == 0 then + -- If there are no handlers, then there won't be any effect, so skip calculations + return + end + + if useType > 3 or useType < 0 then + print('Error: Unknown useType: '..tostring(useType)) + return + end + + -- Compute skill gain + local skillStat = NPC.stats.skills[skillid](self) + local skillGainUnorm = getSkillGainValue(skillid, useType, scale) + local skillProgressRequirementUnorm = getSkillProgressRequirementUnorm(self, skillid) + local skillGain = skillGainUnorm / skillProgressRequirementUnorm + + -- Put skill gain in a table so that handlers can modify it + local parameters = { + skillGain = skillGain, + } + + for i = #skillUsedHandlers, 1, -1 do + if skillUsedHandlers[i](skillid, useType, parameters) == false then + return + end + end +end + +local function skillLevelUp(skillid, source) + if #skillLevelUpHandlers == 0 then + -- If there are no handlers, then there won't be any effect, so skip calculations + return + end + + local skillRecord = Skill.record(skillid) + local npcRecord = NPC.record(self) + local class = NPC.classes.record(npcRecord.class) + + local levelUpProgress = 0 + local levelUpAttributeIncreaseValue = core.getGMST('iLevelupMiscMultAttriubte') + + if tableHasValue(class.minorSkills, skillid) then + levelUpProgress = core.getGMST('iLevelUpMinorMult') + levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMinorMultAttribute') + elseif tableHasValue(class.majorSkills, skillid) then + levelUpProgress = core.getGMST('iLevelUpMajorMult') + levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMajorMultAttribute') + end + + local parameters = + { + skillIncreaseValue = 1, + levelUpProgress = levelUpProgress, + levelUpAttribute = skillRecord.attribute, + levelUpAttributeIncreaseValue = levelUpAttributeIncreaseValue, + levelUpSpecialization = skillRecord.specialization, + levelUpSpecializationIncreaseValue = core.getGMST('iLevelupSpecialization'), + } + + for i = #skillLevelUpHandlers, 1, -1 do + if skillLevelUpHandlers[i](skillid, source, parameters) == false then + return + end + end +end + +return { + interfaceName = 'SkillProgression', + --- + -- Allows to extend or override built-in skill progression mechanics. + -- @module SkillProgression + -- @usage local I = require('openmw.interfaces') + -- + -- -- Forbid increasing destruction skill past 50 + -- I.SkillProgression.addSkillLevelUpHandler(function(skillid, options) + -- if skillid == 'destruction' and types.NPC.stats.skills.destruction(self).base >= 50 then + -- return false + -- end + -- end) + -- + -- -- Scale sneak skill progression based on active invisibility effects + -- I.SkillProgression.addSkillUsedHandler(function(skillid, useType, params) + -- if skillid == 'sneak' and useType == I.SkillProgression.SKILL_USE_TYPES.Sneak_AvoidNotice then + -- local activeEffects = Actor.activeEffects(self) + -- local visibility = activeEffects:getEffect(core.magic.EFFECT_TYPE.Chameleon).magnitude / 100 + -- visibility = visibility + activeEffects:getEffect(core.magic.EFFECT_TYPE.Invisibility).magnitude + -- visibility = 1 - math.min(1, math.max(0, visibility)) + -- local oldSkillGain = params.skillGain + -- params.skillGain = oldSkillGain * visibility + -- end + -- end + -- + interface = { + --- Interface version + -- @field [parent=#SkillProgression] #number version + version = 0, + + --- Add new skill level up handler for this actor + -- @function [parent=#SkillProgression] addSkillLevelUpHandler + -- @param #function handler The handler, see #skillLevelUpHandler. + addSkillLevelUpHandler = function(handler) + skillLevelUpHandlers[#skillLevelUpHandlers + 1] = handler + end, + + --- Add new skillUsed handler for this actor + -- @function [parent=#SkillProgression] addSkillUsedHandler + -- @param #function handler The handler. + addSkillUsedHandler = function(handler) + skillUsedHandlers[#skillUsedHandlers + 1] = handler + end, + + --- Register a skill use, activating relevant handlers + -- @function [parent=#SkillProgression] skillUsed + -- @param #string skillid The if of the skill that was used + -- @param #SkillUseType useType A number from 0 to 3 (inclusive) representing the way the skill was used, with each use type having a different skill progression rate. Available use types and its effect is skill specific. See @{SkillProgression#skillUseType} + -- @param #number scale A number that linearly scales the skill progress received from this use. Defaults to 1. + skillUsed = skillUsed, + + --- @{#SkillUseType} + -- @field [parent=#SkillProgression] #SkillUseType SKILL_USE_TYPES Available skill usage types + SKILL_USE_TYPES = { + -- These are shared by multiple skills + Armor_HitByOpponent = 0, + Block_Success = 0, + Spellcast_Success = 0, + Weapon_SuccessfulHit = 0, + + -- Skill-specific use types + Alchemy_CreatePotion = 0, + Alchemy_UseIngredient = 1, + Enchant_Recharge = 0, + Enchant_UseMagicItem = 1, + Enchant_CreateMagicItem = 2, + Enchant_CastOnStrike = 3, + Acrobatics_Jump = 0, + Acrobatics_Fall = 1, + Mercantile_Success = 0, + Mercantile_Bribe = 1, -- Note: This is bugged in vanilla and is not actually in use. + Security_DisarmTrap = 0, + Security_PickLock = 1, + Sneak_AvoidNotice = 0, + Sneak_PickPocket = 1, + Speechcraft_Success = 0, + Speechcraft_Fail = 1, + Armorer_Repair = 0, + Athletics_RunOneSecond = 0, + Athletics_SwimOneSecond = 0, + }, + + --- Register a skill increase, activating relevant handlers + -- @function [parent=#SkillProgression] skillUsed + -- @param #string skillid The id of the skill to be increased. + -- @param #SkillLevelUpSource source The source of the skill increase. + skillLevelUp = skillLevelUp, + + --- @{#SkillLevelUpSource} + -- @field [parent=#SkillProgression] #SkillLevelUpSource SKILL_INCREASE_SOURCES + SKILL_INCREASE_SOURCES = { + Book = 'book', + Usage = 'usage', + Trainer = 'trainer', + }, + }, + engineHandlers = { _onSkillUse = skillUsed }, +} diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 94d63312f1..6babfa6c85 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -875,6 +875,10 @@ -- @field #string specialization Skill specialization. Either combat, magic, or stealth. -- @field #MagicSchoolData school Optional magic school -- @field #string attribute The id of the skill's governing attribute +-- @field #string skillGain1 1 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) +-- @field #string skillGain2 2 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) +-- @field #string skillGain3 3 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) +-- @field #string skillGain4 4 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) --- -- @type MagicSchoolData diff --git a/files/lua_api/openmw/interfaces.lua b/files/lua_api/openmw/interfaces.lua index 57103768d2..5ed98bd8be 100644 --- a/files/lua_api/openmw/interfaces.lua +++ b/files/lua_api/openmw/interfaces.lua @@ -26,6 +26,9 @@ --- -- @field [parent=#interfaces] scripts.omw.usehandlers#scripts.omw.usehandlers ItemUsage +--- +-- @field [parent=#interfaces] scripts.omw.skillhandlers#scripts.omw.skillhandlers SkillProgression + --- -- @function [parent=#interfaces] __index -- @param #interfaces self diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 734288bfb7..c74b599597 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -352,10 +352,29 @@ -- @function [parent=#ActorSpells] clear -- @param self +--- Values affect how much each attribute can be increased at level up, and are all reset to 0 upon level up. +-- @type SkillIncreasesForAttributeStats +-- @field #number agility Number of contributions to agility for the next level up. +-- @field #number endurance Number of contributions to endurance for the next level up. +-- @field #number intelligence Number of contributions to intelligence for the next level up. +-- @field #number luck Number of contributions to luck for the next level up. +-- @field #number personality Number of contributions to personality for the next level up. +-- @field #number speed Number of contributions to speed for the next level up. +-- @field #number strength Number of contributions to strength for the next level up. +-- @field #number willpower Number of contributions to willpower for the next level up. + +--- Values affect the graphic used on the level up screen, and are all reset to 0 upon level up. +-- @type SkillIncreasesForSpecializationStats +-- @field #number combat Number of contributions to combat specialization for the next level up. +-- @field #number magic Number of contributions to magic specialization for the next level up. +-- @field #number stealth Number of contributions to stealth specialization for the next level up. + --- -- @type LevelStat -- @field #number current The actor's current level. --- @field #number progress The NPC's level progress (read-only.) +-- @field #number progress The NPC's level progress. +-- @field #SkillIncreasesForAttributeStats skillIncreasesForAttribute The NPC's attribute contributions towards the next level up. Values affect how much each attribute can be increased at level up. +-- @field #SkillIncreasesForSpecializationStats skillIncreasesForSpecialization The NPC's attribute contributions towards the next level up. Values affect the graphic used on the level up screen. --- -- @type DynamicStat From 80e9631abd303ee4f5ae6f92ca5f96dfd05d0179 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 15 Jan 2024 20:29:35 +0100 Subject: [PATCH 013/451] Use ESM::Class::Specialization for parameters to npcstats --- apps/openmw/mwgui/levelupdialog.cpp | 6 ++++-- apps/openmw/mwlua/stats.cpp | 6 ++++-- apps/openmw/mwmechanics/npcstats.cpp | 4 ++-- apps/openmw/mwmechanics/npcstats.hpp | 5 +++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 2160a04b1b..4950e3edf4 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -164,8 +164,10 @@ namespace MWGui const MWMechanics::NpcStats& pcStats = player.getClass().getNpcStats(player); setClassImage(mClassImage, - ESM::RefId::stringRefId(getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(0), - pcStats.getSkillIncreasesForSpecialization(1), pcStats.getSkillIncreasesForSpecialization(2)))); + ESM::RefId::stringRefId( + getLevelupClassImage(pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Combat), + pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Magic), + pcStats.getSkillIncreasesForSpecialization(ESM::Class::Specialization::Stealth)))); int level = creatureStats.getLevel() + 1; mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level)); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 224fdba0f2..4257a90e84 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -89,7 +89,8 @@ namespace MWLua stats.setSkillIncreasesForAttribute( *std::get(index).getIf(), LuaUtil::cast(value)); else if (prop == "skillIncreasesForSpecialization") - stats.setSkillIncreasesForSpecialization(std::get(index), LuaUtil::cast(value)); + stats.setSkillIncreasesForSpecialization( + static_cast(std::get(index)), LuaUtil::cast(value)); } class SkillIncreasesForAttributeStats @@ -144,7 +145,8 @@ namespace MWLua return getValue(context, mObject, &setNpcValue, specialization, "skillIncreasesForSpecialization", [specialization](const MWWorld::Ptr& ptr) { - return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForSpecialization(specialization); + return ptr.getClass().getNpcStats(ptr).getSkillIncreasesForSpecialization( + static_cast(specialization)); }); } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 046c94fc8f..2c7a710355 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -365,12 +365,12 @@ void MWMechanics::NpcStats::setSkillIncreasesForAttribute(ESM::Attribute::Attrib mSkillIncreases[attribute] = increases; } -int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(int spec) const +int MWMechanics::NpcStats::getSkillIncreasesForSpecialization(ESM::Class::Specialization spec) const { return mSpecIncreases[spec]; } -void MWMechanics::NpcStats::setSkillIncreasesForSpecialization(int spec, int increases) +void MWMechanics::NpcStats::setSkillIncreasesForSpecialization(ESM::Class::Specialization spec, int increases) { assert(spec >= 0 && spec < 3); mSpecIncreases[spec] = increases; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 9695d45ac4..0104153a38 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -3,6 +3,7 @@ #include "creaturestats.hpp" #include +#include #include #include #include @@ -98,8 +99,8 @@ namespace MWMechanics int getSkillIncreasesForAttribute(ESM::Attribute::AttributeID attribute) const; void setSkillIncreasesForAttribute(ESM::Attribute::AttributeID, int increases); - int getSkillIncreasesForSpecialization(int spec) const; - void setSkillIncreasesForSpecialization(int spec, int increases); + int getSkillIncreasesForSpecialization(ESM::Class::Specialization spec) const; + void setSkillIncreasesForSpecialization(ESM::Class::Specialization spec, int increases); void levelUp(); From e1a22242d9460a1bf39f19967b5ae421fade7022 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 15 Jan 2024 20:40:38 +0100 Subject: [PATCH 014/451] skillGain as a table --- apps/openmw/mwlua/stats.cpp | 15 +++++++-------- files/data/scripts/omw/skillhandlers.lua | 22 +++------------------- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 4257a90e84..c6492c1ec2 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -604,14 +604,13 @@ namespace MWLua skillT["attribute"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string { return ESM::Attribute::indexToRefId(rec.mData.mAttribute).serializeText(); }); - skillT["skillGain1"] - = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[0]; }); - skillT["skillGain2"] - = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[1]; }); - skillT["skillGain3"] - = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[2]; }); - skillT["skillGain4"] - = sol::readonly_property([](const ESM::Skill& rec) -> float { return rec.mData.mUseValue[3]; }); + skillT["skillGain"] = sol::readonly_property([lua](const ESM::Skill& rec) -> sol::table { + sol::table res(lua, sol::create); + int index = 1; + for (auto skillGain : rec.mData.mUseValue) + res[index++] = skillGain; + return res; + }); auto schoolT = context.mLua->sol().new_usertype("MagicSchool"); schoolT[sol::meta_function::to_string] diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index a4fb42ebde..e27925c750 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -77,24 +77,6 @@ local function tableHasValue(table, value) return false end -local function getSkillGainValue(skillid, useType, scale) - local skillRecord = Skill.record(skillid) - - local skillGain = 0 - if useType == 0 then - skillGain = skillRecord.skillGain1 - elseif useType == 1 then - skillGain = skillRecord.skillGain2 - elseif useType == 2 then - skillGain = skillRecord.skillGain3 - elseif useType == 3 then - skillGain = skillRecord.skillGain4 - end - - if scale ~= nil then skillGain = skillGain * scale end - return skillGain -end - local function getSkillProgressRequirementUnorm(npc, skillid) local npcRecord = NPC.record(npc) local class = NPC.classes.record(npcRecord.class) @@ -128,7 +110,9 @@ local function skillUsed(skillid, useType, scale) -- Compute skill gain local skillStat = NPC.stats.skills[skillid](self) - local skillGainUnorm = getSkillGainValue(skillid, useType, scale) + local skillRecord = Skill.record(skillid) + local skillGainUnorm = skillRecord.skillGain[useType + 1] + if scale then skillGainUnorm = skillGainUnorm * scale end local skillProgressRequirementUnorm = getSkillProgressRequirementUnorm(self, skillid) local skillGain = skillGainUnorm / skillProgressRequirementUnorm From 7755c97fdf6233f603cdcfc14812647374664715 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 15 Jan 2024 21:03:46 +0100 Subject: [PATCH 015/451] update docs --- files/lua_api/openmw/core.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 6babfa6c85..e15c2224fb 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -875,10 +875,7 @@ -- @field #string specialization Skill specialization. Either combat, magic, or stealth. -- @field #MagicSchoolData school Optional magic school -- @field #string attribute The id of the skill's governing attribute --- @field #string skillGain1 1 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) --- @field #string skillGain2 2 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) --- @field #string skillGain3 3 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) --- @field #string skillGain4 4 of 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType) +-- @field #table skillGain Table of the 4 possible skill gain values. See [SkillProgression#SkillUseType](interface_skill_progression.html#SkillUseType). --- -- @type MagicSchoolData From 2cfa819ccca07c3f6fe9fdc7b6b6d3ef7889b94f Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 15 Jan 2024 21:24:40 +0100 Subject: [PATCH 016/451] Use interface in _onSkillUse engine handler. --- files/data/scripts/omw/skillhandlers.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index e27925c750..975a8b984e 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -1,4 +1,5 @@ local self = require('openmw.self') +local I = require('openmw.interfaces') local types = require('openmw.types') local core = require('openmw.core') local NPC = require('openmw.types').NPC @@ -263,5 +264,10 @@ return { Trainer = 'trainer', }, }, - engineHandlers = { _onSkillUse = skillUsed }, + engineHandlers = { + _onSkillUse = function (skillid, useType, scale) + -- Use the interface here so any overrides of skillUsed will receive the call. + I.SkillProgression.skillUsed(skillid, useType, scale) + end, + }, } From 9f15f3b431e057232ada7fb39748a4a4e37a6d2e Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 15 Jan 2024 21:48:19 +0100 Subject: [PATCH 017/451] Add engine handler for skill levelup, to dehardcode the book/trainer case. --- apps/openmw/mwbase/luamanager.hpp | 3 ++- apps/openmw/mwgui/trainingwindow.cpp | 6 ++---- apps/openmw/mwlua/engineevents.cpp | 9 +++++++++ apps/openmw/mwlua/engineevents.hpp | 8 +++++++- apps/openmw/mwlua/localscripts.cpp | 2 +- apps/openmw/mwlua/localscripts.hpp | 5 +++++ apps/openmw/mwlua/luamanagerimp.cpp | 5 +++++ apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwworld/actionread.cpp | 8 ++------ files/data/scripts/omw/skillhandlers.lua | 5 ++++- 10 files changed, 38 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 115416cd8c..7f7ee85127 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -62,8 +62,9 @@ namespace MWBase virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) = 0; virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0; - virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0; virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname, + virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0; + virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0; const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) = 0; diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 5395f6db1c..fa4fd266b5 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -5,6 +5,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -174,10 +175,7 @@ namespace MWGui } // increase skill - MWWorld::LiveCellRef* playerRef = player.get(); - - const ESM::Class* class_ = store.get().find(playerRef->mBase->mClass); - pcStats.increaseSkill(skill->mId, *class_, true); + MWBase::Environment::get().getLuaManager()->skillLevelUp(player, skill->mId, "trainer"); // remove gold player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price); diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 7cc2b3db48..f9b9d461cc 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -104,6 +104,15 @@ namespace MWLua scripts->onSkillUse(event.mSkill, event.useType, event.scale); } + void operator()(const OnSkillLevelUp& event) const + { + MWWorld::Ptr actor = getPtr(event.mActor); + if (actor.isEmpty()) + return; + if (auto* scripts = getLocalScripts(actor)) + scripts->onSkillLevelUp(event.mSkill, event.mSource); + } + private: MWWorld::Ptr getPtr(ESM::RefNum id) const { diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index b9f4c25b39..862c09ef2a 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -70,8 +70,14 @@ namespace MWLua int useType; float scale; }; + struct OnSkillLevelUp + { + ESM::RefNum mActor; + std::string mSkill; + std::string mSource; + }; using Event = std::variant; + OnAnimationTextKey, OnSkillUse, OnSkillLevelUp>; void clear() { mQueue.clear(); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); } diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 0c0af5b902..8bd0d7c047 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -176,7 +176,7 @@ namespace MWLua { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, - &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse }); + &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse, &mOnSkillLevelUp }); } void LocalScripts::setActive(bool active) diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index f5f740b1c6..2ec78860d1 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -83,6 +83,10 @@ namespace MWLua { callEngineHandlers(mOnSkillUse, skillId, useType, scale); } + void onSkillLevelUp(std::string_view skillId, std::string_view source) + { + callEngineHandlers(mOnSkillLevelUp, skillId, source); + } void applyStatsCache(); @@ -98,6 +102,7 @@ namespace MWLua EngineHandlerList mOnAnimationTextKeyHandlers{ "_onAnimationTextKey" }; EngineHandlerList mOnPlayAnimationHandlers{ "_onPlayAnimation" }; EngineHandlerList mOnSkillUse{ "_onSkillUse" }; + EngineHandlerList mOnSkillLevelUp{ "_onSkillLevelUp" }; }; } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 0974856d25..5a743f41e3 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -411,6 +411,11 @@ namespace MWLua mEngineEvents.addToQueue(EngineEvents::OnSkillUse{ getId(actor), skillId.serializeText(), useType, scale }); } + void LuaManager::skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) + { + mEngineEvents.addToQueue(EngineEvents::OnSkillLevelUp{ getId(actor), skillId.serializeText(), std::string(source) }); + } + void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) { mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 2e29ff272a..685fbbde4c 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -85,6 +85,7 @@ namespace MWLua std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) override; void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) override; + void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) override; void exteriorCreated(MWWorld::CellStore& cell) override { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); diff --git a/apps/openmw/mwworld/actionread.cpp b/apps/openmw/mwworld/actionread.cpp index 3f48920dc0..477c92d2dd 100644 --- a/apps/openmw/mwworld/actionread.cpp +++ b/apps/openmw/mwworld/actionread.cpp @@ -5,6 +5,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/actorutil.hpp" @@ -49,12 +50,7 @@ namespace MWWorld ESM::RefId skill = ESM::Skill::indexToRefId(ref->mBase->mData.mSkillId); if (!skill.empty() && !npcStats.hasBeenUsed(ref->mBase->mId)) { - MWWorld::LiveCellRef* playerRef = actor.get(); - - const ESM::Class* class_ - = MWBase::Environment::get().getESMStore()->get().find(playerRef->mBase->mClass); - - npcStats.increaseSkill(skill, *class_, true, true); + MWBase::Environment::get().getLuaManager()->skillLevelUp(actor, skill, "book"); npcStats.flagAsUsed(ref->mBase->mId); } diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index 975a8b984e..1f3b856344 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -265,9 +265,12 @@ return { }, }, engineHandlers = { + -- Use the interface in these handlers so any overrides will receive the calls. _onSkillUse = function (skillid, useType, scale) - -- Use the interface here so any overrides of skillUsed will receive the call. I.SkillProgression.skillUsed(skillid, useType, scale) end, + _onSkillLevelUp = function (skillid, source) + I.SkillProgression.skillLevelUp(skillid, source) + end, }, } From 264a8c06690e2039a6d8afee6a81e9e17c708527 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 15 Jan 2024 21:48:46 +0100 Subject: [PATCH 018/451] Remove the now unused npcstats methods useSkill and increaseSkill --- apps/openmw/mwmechanics/npcstats.cpp | 84 ---------------------------- apps/openmw/mwmechanics/npcstats.hpp | 5 -- 2 files changed, 89 deletions(-) diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 2c7a710355..eceaf8b482 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -209,90 +209,6 @@ float MWMechanics::NpcStats::getSkillProgressRequirement(ESM::RefId id, const ES return progressRequirement; } -void MWMechanics::NpcStats::useSkill(ESM::RefId id, const ESM::Class& class_, int usageType, float extraFactor) -{ - const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get().find(id); - float skillGain = 1; - if (usageType >= 4) - throw std::runtime_error("skill usage type out of range"); - if (usageType >= 0) - { - skillGain = skill->mData.mUseValue[usageType]; - if (skillGain < 0) - throw std::runtime_error("invalid skill gain factor"); - } - skillGain *= extraFactor; - - MWMechanics::SkillValue& value = getSkill(skill->mId); - - value.setProgress(value.getProgress() + skillGain); - - if (int(value.getProgress()) >= int(getSkillProgressRequirement(skill->mId, class_))) - { - // skill levelled up - increaseSkill(skill->mId, class_, false); - } -} - -void MWMechanics::NpcStats::increaseSkill(ESM::RefId id, const ESM::Class& class_, bool preserveProgress, bool readBook) -{ - const ESM::Skill* skill = MWBase::Environment::get().getESMStore()->get().find(id); - float base = getSkill(skill->mId).getBase(); - - if (base >= 100.f) - return; - - base += 1; - - const MWWorld::Store& gmst = MWBase::Environment::get().getESMStore()->get(); - - // is this a minor or major skill? - int increase = gmst.find("iLevelupMiscMultAttriubte")->mValue.getInteger(); // Note: GMST has a typo - int index = ESM::Skill::refIdToIndex(skill->mId); - for (const auto& skills : class_.mData.mSkills) - { - if (skills[0] == index) - { - mLevelProgress += gmst.find("iLevelUpMinorMult")->mValue.getInteger(); - increase = gmst.find("iLevelUpMinorMultAttribute")->mValue.getInteger(); - break; - } - else if (skills[1] == index) - { - mLevelProgress += gmst.find("iLevelUpMajorMult")->mValue.getInteger(); - increase = gmst.find("iLevelUpMajorMultAttribute")->mValue.getInteger(); - break; - } - } - - mSkillIncreases[ESM::Attribute::indexToRefId(skill->mData.mAttribute)] += increase; - - mSpecIncreases[skill->mData.mSpecialization] += gmst.find("iLevelupSpecialization")->mValue.getInteger(); - - // Play sound & skill progress notification - /// \todo check if character is the player, if levelling is ever implemented for NPCs - MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("skillraise")); - - std::string message{ MWBase::Environment::get().getWindowManager()->getGameSettingString("sNotifyMessage39", {}) }; - message = Misc::StringUtils::format( - message, MyGUI::TextIterator::toTagsString(skill->mName).asUTF8(), static_cast(base)); - - if (readBook) - message = "#{sBookSkillMessage}\n" + message; - - MWBase::Environment::get().getWindowManager()->messageBox(message, MWGui::ShowInDialogueMode_Never); - - if (mLevelProgress >= gmst.find("iLevelUpTotal")->mValue.getInteger()) - { - // levelup is possible now - MWBase::Environment::get().getWindowManager()->messageBox("#{sLevelUpMsg}", MWGui::ShowInDialogueMode_Never); - } - - getSkill(skill->mId).setBase(base); - if (!preserveProgress) - getSkill(skill->mId).setProgress(0); -} - int MWMechanics::NpcStats::getLevelProgress() const { return mLevelProgress; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 0104153a38..f94744cb71 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -87,11 +87,6 @@ namespace MWMechanics float getSkillProgressRequirement(ESM::RefId id, const ESM::Class& class_) const; - void useSkill(ESM::RefId id, const ESM::Class& class_, int usageType = -1, float extraFactor = 1.f); - ///< Increase skill by usage. - - void increaseSkill(ESM::RefId id, const ESM::Class& class_, bool preserveProgress, bool readBook = false); - int getLevelProgress() const; void setLevelProgress(int progress); From 055e9a5055407aed00f1741130365cc222afdc69 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 21 Jan 2024 15:12:07 +0100 Subject: [PATCH 019/451] clang'd --- apps/openmw/mwlua/luamanagerimp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 5a743f41e3..07ad612ec3 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -413,7 +413,8 @@ namespace MWLua void LuaManager::skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) { - mEngineEvents.addToQueue(EngineEvents::OnSkillLevelUp{ getId(actor), skillId.serializeText(), std::string(source) }); + mEngineEvents.addToQueue( + EngineEvents::OnSkillLevelUp{ getId(actor), skillId.serializeText(), std::string(source) }); } void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) From e0e968a082ffb97f93866ac9c81a5161c9ea9d77 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 30 Jan 2024 00:05:57 +0100 Subject: [PATCH 020/451] rebase errors --- apps/openmw/mwbase/luamanager.hpp | 4 ++-- apps/openmw/mwlua/engineevents.cpp | 2 +- apps/openmw/mwlua/engineevents.hpp | 6 ------ apps/openmw/mwlua/localscripts.cpp | 3 ++- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 7f7ee85127..db5b51165d 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -63,11 +63,11 @@ namespace MWBase virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) = 0; virtual void animationTextKey(const MWWorld::Ptr& actor, const std::string& key) = 0; virtual void playAnimation(const MWWorld::Ptr& object, const std::string& groupname, - virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0; - virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0; const MWRender::AnimPriority& priority, int blendMask, bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint, uint32_t loops, bool loopfallback) = 0; + virtual void skillLevelUp(const MWWorld::Ptr& actor, ESM::RefId skillId, std::string_view source) = 0; + virtual void skillUse(const MWWorld::Ptr& actor, ESM::RefId skillId, int useType, float scale) = 0; virtual void exteriorCreated(MWWorld::CellStore& cell) = 0; virtual void actorDied(const MWWorld::Ptr& actor) = 0; virtual void questUpdated(const ESM::RefId& questId, int stage) = 0; diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index f9b9d461cc..6c652bccba 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -94,7 +94,7 @@ namespace MWLua if (auto* scripts = getLocalScripts(actor)) scripts->onAnimationTextKey(event.mGroupname, event.mKey); } - + void operator()(const OnSkillUse& event) const { MWWorld::Ptr actor = getPtr(event.mActor); diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index 862c09ef2a..fb9183eb7c 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -57,12 +57,6 @@ namespace MWLua std::string mGroupname; std::string mKey; }; - struct OnAnimationTextKey - { - ESM::RefNum mActor; - std::string mGroupname; - std::string mKey; - }; struct OnSkillUse { ESM::RefNum mActor; diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 8bd0d7c047..8fa0571afc 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -176,7 +176,8 @@ namespace MWLua { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, - &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse, &mOnSkillLevelUp }); + &mOnTeleportedHandlers, &mOnAnimationTextKeyHandlers, &mOnPlayAnimationHandlers, &mOnSkillUse, + &mOnSkillLevelUp }); } void LocalScripts::setActive(bool active) From 4f8476efd4cf69b13eb769ef47a92159b7f626fc Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 31 Jan 2024 23:01:16 +0100 Subject: [PATCH 021/451] Comments --- .../lua-scripting/interface_skill_progression.rst | 2 +- docs/source/reference/lua-scripting/tables/interfaces.rst | 2 +- files/data/scripts/omw/mechanics/playercontroller.lua | 4 +--- files/data/scripts/omw/skillhandlers.lua | 8 ++++---- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/source/reference/lua-scripting/interface_skill_progression.rst b/docs/source/reference/lua-scripting/interface_skill_progression.rst index 5cd2e87812..f27f03d556 100644 --- a/docs/source/reference/lua-scripting/interface_skill_progression.rst +++ b/docs/source/reference/lua-scripting/interface_skill_progression.rst @@ -1,5 +1,5 @@ Interface SkillProgression -=================== +========================== .. raw:: html :file: generated_html/scripts_omw_skillhandlers.html diff --git a/docs/source/reference/lua-scripting/tables/interfaces.rst b/docs/source/reference/lua-scripting/tables/interfaces.rst index 0692e60b1c..f2e5921b02 100644 --- a/docs/source/reference/lua-scripting/tables/interfaces.rst +++ b/docs/source/reference/lua-scripting/tables/interfaces.rst @@ -28,7 +28,7 @@ * - :ref:`SkillProgression ` - by local scripts - | Control, extend, and override skill progression of the - - | player. + | player. * - :ref:`Settings ` - by player and global scripts - Save, display and track changes of setting values. diff --git a/files/data/scripts/omw/mechanics/playercontroller.lua b/files/data/scripts/omw/mechanics/playercontroller.lua index 29baf32020..935bf5029f 100644 --- a/files/data/scripts/omw/mechanics/playercontroller.lua +++ b/files/data/scripts/omw/mechanics/playercontroller.lua @@ -68,9 +68,7 @@ local function skillLevelUpHandler(skillid, source, params) ambient.playSound("skillraise") - local message = core.getGMST('sNotifyMessage39') - message = message:gsub("%%s", skillRecord.name) - message = message:gsub("%%d", tostring(skillStat.base)) + local message = string.format(core.getGMST('sNotifyMessage39'),skillRecord.name,skillStat.base) if source == I.SkillProgression.SKILL_INCREASE_SOURCES.Book then message = '#{sBookSkillMessage}\n'..message diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index 1f3b856344..30188f2341 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -212,7 +212,7 @@ return { skillUsedHandlers[#skillUsedHandlers + 1] = handler end, - --- Register a skill use, activating relevant handlers + --- Trigger a skill use, activating relevant handlers -- @function [parent=#SkillProgression] skillUsed -- @param #string skillid The if of the skill that was used -- @param #SkillUseType useType A number from 0 to 3 (inclusive) representing the way the skill was used, with each use type having a different skill progression rate. Available use types and its effect is skill specific. See @{SkillProgression#skillUseType} @@ -250,9 +250,9 @@ return { Athletics_SwimOneSecond = 0, }, - --- Register a skill increase, activating relevant handlers - -- @function [parent=#SkillProgression] skillUsed - -- @param #string skillid The id of the skill to be increased. + --- Trigger a skill level up, activating relevant handlers + -- @function [parent=#SkillProgression] skillLevelUp + -- @param #string skillid The id of the skill to level up. -- @param #SkillLevelUpSource source The source of the skill increase. skillLevelUp = skillLevelUp, From 3882ba15fbc680019a3483a7495afc5f147125ad Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 2 Feb 2024 20:10:25 +0100 Subject: [PATCH 022/451] Rework documentation of skill used/level up handlers. --- files/data/scripts/omw/skillhandlers.lua | 55 +++++++++++------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index 30188f2341..f6a8ec4248 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -41,32 +41,6 @@ local Skill = core.stats.Skill -- @field #string Trainer trainer -- @field #string Usage usage ---- --- Table of valid handler signatures --- @type HandlerSignatures --- - ---- Signature of the skillLevelUp handler --- @function [parent=#HandlerSignatures] skillLevelUpHandler --- @param #string skillid The ID of the skill being leveled up --- @param #SkillLevelUpSource source The source of the skill level up --- @param #table params Modifiable skill level up values as a table. These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values: --- --- * `skillIncreaseValue` - The numeric amount of skill levels gained. --- * `levelUpProgress` - The numeric amount of level up progress gained. --- * `levelUpAttribute` - The string identifying the attribute that should receive points from this skill level up. --- * `levelUpAttributeIncreaseValue` - The numeric amount of attribute increase points received. This contributes to the amount of each attribute the character receives during a vanilla level up. --- * `levelUpSpecialization` - The string identifying the specialization that should receive points from this skill level up. --- * `levelUpSpecializationIncreaseValue` - The numeric amount of specialization increase points received. This contributes to the icon displayed at the level up screen during a vanilla level up. - ---- Signature of the skillUsed handler --- @function [parent=#HandlerSignatures] skillUsedHandler --- @param #string skillid The ID of the skill being progressed --- @param #SkillUseType useType The use type the skill progression --- @param #table params Modifiable skill progression value. By default contains the single value --- --- * `skillGain` - The numeric amount of skill progress gained, normalized to the range 0 to 1, where 1 is a full level. - local skillUsedHandlers = {} local skillLevelUpHandlers = {} @@ -118,12 +92,12 @@ local function skillUsed(skillid, useType, scale) local skillGain = skillGainUnorm / skillProgressRequirementUnorm -- Put skill gain in a table so that handlers can modify it - local parameters = { + local options = { skillGain = skillGain, } for i = #skillUsedHandlers, 1, -1 do - if skillUsedHandlers[i](skillid, useType, parameters) == false then + if skillUsedHandlers[i](skillid, useType, options) == false then return end end @@ -150,7 +124,7 @@ local function skillLevelUp(skillid, source) levelUpAttributeIncreaseValue = core.getGMST('iLevelUpMajorMultAttribute') end - local parameters = + local options = { skillIncreaseValue = 1, levelUpProgress = levelUpProgress, @@ -161,7 +135,7 @@ local function skillLevelUp(skillid, source) } for i = #skillLevelUpHandlers, 1, -1 do - if skillLevelUpHandlers[i](skillid, source, parameters) == false then + if skillLevelUpHandlers[i](skillid, source, options) == false then return end end @@ -199,13 +173,32 @@ return { version = 0, --- Add new skill level up handler for this actor + -- If `handler(skillid, source, options)` returns false, other handlers (including the default skill level up handler) + -- will be skipped. Where skillid and source are the parameters passed to @{SkillProgression#skillLevelUp}, and options is + -- a modifiable table of skill level up values, and can be modified to change the behavior of later handlers. + -- These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values: + -- + -- * `skillIncreaseValue` - The numeric amount of skill levels gained. + -- * `levelUpProgress` - The numeric amount of level up progress gained. + -- * `levelUpAttribute` - The string identifying the attribute that should receive points from this skill level up. + -- * `levelUpAttributeIncreaseValue` - The numeric amount of attribute increase points received. This contributes to the amount of each attribute the character receives during a vanilla level up. + -- * `levelUpSpecialization` - The string identifying the specialization that should receive points from this skill level up. + -- * `levelUpSpecializationIncreaseValue` - The numeric amount of specialization increase points received. This contributes to the icon displayed at the level up screen during a vanilla level up. + -- -- @function [parent=#SkillProgression] addSkillLevelUpHandler - -- @param #function handler The handler, see #skillLevelUpHandler. + -- @param #function handler The handler. addSkillLevelUpHandler = function(handler) skillLevelUpHandlers[#skillLevelUpHandlers + 1] = handler end, --- Add new skillUsed handler for this actor + -- If `handler(skillid, useType, options)` returns false, other handlers (including the default skill progress handler) + -- will be skipped. Where skillid and useType are the parameters passed to @{SkillProgression#skillUsed}, + -- and options is a modifiable table of skill progression values, and can be modified to change the behavior of later handlers. + -- By default contains the single value: + -- + -- * `skillGain` - The numeric amount of skill progress gained, normalized to the range 0 to 1, where 1 is a full level. + -- -- @function [parent=#SkillProgression] addSkillUsedHandler -- @param #function handler The handler. addSkillUsedHandler = function(handler) From 55285b5e57d60ecc16fb3f6cbea079f3e260e9dc Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 3 Feb 2024 10:43:38 -0600 Subject: [PATCH 023/451] Fix Global Iteration --- apps/openmw/mwlua/mwscriptbindings.cpp | 75 ++++++++++++++++++++------ 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index a41ef30a44..b2872ed4fc 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -125,21 +125,39 @@ namespace MWLua return "ESM3_GlobalStore{" + std::to_string(store.getSize()) + " globals}"; }; globalStoreT[sol::meta_function::length] = [](const GlobalStore& store) { return store.getSize(); }; - globalStoreT[sol::meta_function::index] - = sol::overload([](const GlobalStore& store, std::string_view globalId) -> sol::optional { - auto g = store.search(ESM::RefId::deserializeText(globalId)); - if (g == nullptr) - return sol::nullopt; - char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); - if (varType == 's' || varType == 'l') - { - return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); - } - else - { - return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); - } - }); + globalStoreT[sol::meta_function::index] = sol::overload( + [](const GlobalStore& store, std::string_view globalId) -> sol::optional { + auto g = store.search(ESM::RefId::deserializeText(globalId)); + if (g == nullptr) + return sol::nullopt; + char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); + if (varType == 's' || varType == 'l') + { + return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); + } + else + { + return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); + } + }, + [](const GlobalStore& store, int index) -> sol::optional { + if (index < 1 || index >= store.getSize()) + return sol::nullopt; + auto g = store.at(index - 1); + if (g == nullptr) + return sol::nullopt; + std::string globalId = g->mId.serializeText(); + char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); + if (varType == 's' || varType == 'l') + { + return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); + } + else + { + return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); + } + }); + globalStoreT[sol::meta_function::new_index] = sol::overload([](const GlobalStore& store, std::string_view globalId, float val) { auto g = store.search(ESM::RefId::deserializeText(globalId)); @@ -155,7 +173,32 @@ namespace MWLua MWBase::Environment::get().getWorld()->setGlobalFloat(globalId, val); } }); - globalStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + globalStoreT[sol::meta_function::pairs] = [](const GlobalStore& store) { + int index = 0; // Start index + return sol::as_function( + [index, &store](sol::this_state ts) mutable -> sol::optional> { + if (index >= store.getSize()) + return sol::nullopt; + + const auto& global = store.at(index++); + if (!global) + return sol::nullopt; + + std::string globalId = global->mId.serializeText(); + char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); + float value; + if (varType == 's' || varType == 'l') + { + value = static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); + } + else + { + value = MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); + } + + return std::make_tuple(globalId, value); + }); + }; globalStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); api["getGlobalVariables"] = [globalStore](sol::optional player) { if (player.has_value() && player->ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) From 9daf10c305a178cce510aac8548da65fa8305af6 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 3 Feb 2024 10:45:24 -0600 Subject: [PATCH 024/451] Remove comment --- apps/openmw/mwlua/mwscriptbindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index b2872ed4fc..e234a2be8e 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -174,7 +174,7 @@ namespace MWLua } }); globalStoreT[sol::meta_function::pairs] = [](const GlobalStore& store) { - int index = 0; // Start index + int index = 0; return sol::as_function( [index, &store](sol::this_state ts) mutable -> sol::optional> { if (index >= store.getSize()) From 5f9acbd0f0a64030d3bc30a352d46b675bdce778 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 3 Feb 2024 13:03:23 -0600 Subject: [PATCH 025/451] Add function to replace duplicated code --- apps/openmw/mwlua/mwscriptbindings.cpp | 57 +++++++++----------------- 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index e234a2be8e..ded00dc2b2 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -53,6 +53,20 @@ namespace sol namespace MWLua { + auto getGlobalVariableValue(const std::string_view globalId) -> float + { + char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); + if (varType == 'f') + { + return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); + } + else if (varType == 's' || varType == 'l') + { + return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); + } + return 0; + }; + sol::table initMWScriptBindings(const Context& context) { sol::table api(context.mLua->sol(), sol::create); @@ -130,15 +144,7 @@ namespace MWLua auto g = store.search(ESM::RefId::deserializeText(globalId)); if (g == nullptr) return sol::nullopt; - char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); - if (varType == 's' || varType == 'l') - { - return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); - } - else - { - return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); - } + return getGlobalVariableValue(globalId); }, [](const GlobalStore& store, int index) -> sol::optional { if (index < 1 || index >= store.getSize()) @@ -147,15 +153,7 @@ namespace MWLua if (g == nullptr) return sol::nullopt; std::string globalId = g->mId.serializeText(); - char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); - if (varType == 's' || varType == 'l') - { - return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); - } - else - { - return MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); - } + return getGlobalVariableValue(globalId); }); globalStoreT[sol::meta_function::new_index] @@ -163,15 +161,7 @@ namespace MWLua auto g = store.search(ESM::RefId::deserializeText(globalId)); if (g == nullptr) throw std::runtime_error("No variable \"" + std::string(globalId) + "\" in GlobalStore"); - char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); - if (varType == 's' || varType == 'l') - { - MWBase::Environment::get().getWorld()->setGlobalInt(globalId, static_cast(val)); - } - else - { - MWBase::Environment::get().getWorld()->setGlobalFloat(globalId, val); - } + return getGlobalVariableValue(globalId); }); globalStoreT[sol::meta_function::pairs] = [](const GlobalStore& store) { int index = 0; @@ -182,19 +172,10 @@ namespace MWLua const auto& global = store.at(index++); if (!global) - return sol::nullopt; + return sol::nullopt; std::string globalId = global->mId.serializeText(); - char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); - float value; - if (varType == 's' || varType == 'l') - { - value = static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); - } - else - { - value = MWBase::Environment::get().getWorld()->getGlobalFloat(globalId); - } + float value = getGlobalVariableValue(globalId); return std::make_tuple(globalId, value); }); From f7aa9f8d945a73a3a198a8636d13fa3e4a407d7b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 7 Feb 2024 18:08:06 +0100 Subject: [PATCH 026/451] Expose birth signs to Lua --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/birthsignbindings.cpp | 55 +++++++++++++++++++++++++ apps/openmw/mwlua/birthsignbindings.hpp | 13 ++++++ apps/openmw/mwlua/types/player.cpp | 3 ++ files/lua_api/openmw/types.lua | 22 ++++++++++ 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 apps/openmw/mwlua/birthsignbindings.cpp create mode 100644 apps/openmw/mwlua/birthsignbindings.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index cc0cba1a1a..f92a601b82 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -64,7 +64,7 @@ add_openmw_dir (mwlua context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings - classbindings itemdata inputprocessor animationbindings + classbindings itemdata inputprocessor animationbindings birthsignbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp new file mode 100644 index 0000000000..5592179fee --- /dev/null +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "birthsignbindings.hpp" +#include "luamanagerimp.hpp" +#include "types/types.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical> : std::false_type + { + }; +} + +namespace MWLua +{ + sol::table initCoreBirthSignBindings(const Context& context) + { + sol::state_view& lua = context.mLua->sol(); + sol::table birthSigns(context.mLua->sol(), sol::create); + addRecordFunctionBinding(birthSigns, context); + + auto signT = lua.new_usertype("ESM3_BirthSign"); + signT[sol::meta_function::to_string] = [](const ESM::BirthSign& rec) -> std::string { + return "ESM3_BirthSign[" + rec.mId.toDebugString() + "]"; + }; + signT["id"] = sol::readonly_property([](const ESM::BirthSign& rec) { return rec.mId.serializeText(); }); + signT["name"] = sol::readonly_property([](const ESM::BirthSign& rec) -> std::string_view { return rec.mName; }); + signT["description"] + = sol::readonly_property([](const ESM::BirthSign& rec) -> std::string_view { return rec.mDescription; }); + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + signT["texture"] = sol::readonly_property([vfs](const ESM::BirthSign& rec) -> std::string { + return Misc::ResourceHelpers::correctTexturePath(rec.mTexture, vfs); + }); + signT["spells"] = sol::readonly_property([lua](const ESM::BirthSign& rec) -> sol::table { + sol::table res(lua, sol::create); + for (size_t i = 0; i < rec.mPowers.mList.size(); ++i) + res[i + 1] = rec.mPowers.mList[i].serializeText(); + return res; + }); + + return LuaUtil::makeReadOnly(birthSigns); + } +} diff --git a/apps/openmw/mwlua/birthsignbindings.hpp b/apps/openmw/mwlua/birthsignbindings.hpp new file mode 100644 index 0000000000..7c88b8cccb --- /dev/null +++ b/apps/openmw/mwlua/birthsignbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_BIRTHSIGNBINDINGS_H +#define MWLUA_BIRTHSIGNBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initCoreBirthSignBindings(const Context& context); +} + +#endif // MWLUA_BIRTHSIGNBINDINGS_H diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index d2a9c5d920..462bfa888e 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -1,5 +1,6 @@ #include "types.hpp" +#include "../birthsignbindings.hpp" #include "../luamanagerimp.hpp" #include "apps/openmw/mwbase/inputmanager.hpp" @@ -170,5 +171,7 @@ namespace MWLua player["isCharGenFinished"] = [](const Object&) -> bool { return MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1; }; + + player["birthSigns"] = initCoreBirthSignBindings(context); } } diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 4eb8459a6b..a14c44ebdb 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1045,6 +1045,28 @@ -- Values that can be used with getControlSwitch/setControlSwitch. -- @field [parent=#Player] #CONTROL_SWITCH CONTROL_SWITCH +--- @{#BirthSigns}: Birth Sign Data +-- @field [parent=#Player] #BirthSigns birthSigns + +--- +-- A read-only list of all @{#BirthSignRecord}s in the world database. +-- @field [parent=#BirthSigns] #list<#BirthSignRecord> records + +--- +-- Returns a read-only @{#BirthSignRecord} +-- @function [parent=#BirthSigns] record +-- @param #string recordId +-- @return #BirthSignRecord + +--- +-- Birth sign data record +-- @type BirthSignRecord +-- @field #string id Birth sign id +-- @field #string name Birth sign name +-- @field #string description Birth sign description +-- @field #string texture Birth sign texture +-- @field #list<#string> spells A read-only list containing the ids of all spells gained from this sign. + --- -- Send an event to menu scripts. -- @function [parent=#core] sendMenuEvent From f114d409c8ba855f6c01cbb4850c1f6922ab5a7a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 7 Feb 2024 19:35:26 +0100 Subject: [PATCH 027/451] Add get and set birth sign --- apps/openmw/mwlua/types/player.cpp | 28 ++++++++++++++++++++++++++++ files/lua_api/openmw/types.lua | 11 +++++++++++ 2 files changed, 39 insertions(+) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index 462bfa888e..c12f40e832 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -8,7 +8,11 @@ #include "apps/openmw/mwbase/world.hpp" #include "apps/openmw/mwmechanics/npcstats.hpp" #include "apps/openmw/mwworld/class.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwworld/globals.hpp" +#include "apps/openmw/mwworld/player.hpp" + +#include namespace MWLua { @@ -35,6 +39,20 @@ namespace sol }; } +namespace +{ + ESM::RefId toBirthSignId(const sol::object& recordOrId) + { + if (recordOrId.is()) + return recordOrId.as()->mId; + std::string_view textId = LuaUtil::cast(recordOrId); + ESM::RefId id = ESM::RefId::deserializeText(textId); + if (!MWBase::Environment::get().getESMStore()->get().search(id)) + throw std::runtime_error("Failed to find birth sign: " + std::string(textId)); + return id; + } +} + namespace MWLua { static void verifyPlayer(const Object& player) @@ -173,5 +191,15 @@ namespace MWLua }; player["birthSigns"] = initCoreBirthSignBindings(context); + player["getBirthSign"] = [](const Object& player) -> std::string { + verifyPlayer(player); + return MWBase::Environment::get().getWorld()->getPlayer().getBirthSign().serializeText(); + }; + player["setBirthSign"] = [](const Object& player, const sol::object& recordOrId) { + verifyPlayer(player); + if (!dynamic_cast(&player)) + throw std::runtime_error("Only global scripts can change birth signs"); + MWBase::Environment::get().getWorld()->getPlayer().setBirthSign(toBirthSignId(recordOrId)); + }; } } diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index a14c44ebdb..edd2c1170f 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1045,6 +1045,17 @@ -- Values that can be used with getControlSwitch/setControlSwitch. -- @field [parent=#Player] #CONTROL_SWITCH CONTROL_SWITCH +--- +-- @function [parent=#Player] getBirthSign +-- @param openmw.core#GameObject player +-- @return #string The player's birth sign + +--- +-- Can be used only in global scripts. Note that this does not update the player's spells. +-- @function [parent=#Player] setBirthSign +-- @param openmw.core#GameObject player +-- @param #any recordOrId Record or string ID of the birth sign to assign + --- @{#BirthSigns}: Birth Sign Data -- @field [parent=#Player] #BirthSigns birthSigns From 506824cb9d7e4c9ca64c1bc1c2a0d6627e29e3f3 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 2 Feb 2024 22:16:49 +0100 Subject: [PATCH 028/451] Cleanup physics callbacks * Do not copy with allocations. * Remove unused DeepestNotMeContactTestResultCallback. * Avoid using pointers which should not be nullptr. * Move constructors implementation to headers. * Move types defined in .cpp are to unnamed namespace. * Comment unused arguments. * Avoid C-style casts. --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwphysics/actorconvexcallback.cpp | 43 ++++----- apps/openmw/mwphysics/actorconvexcallback.hpp | 11 ++- .../closestnotmerayresultcallback.cpp | 9 -- .../closestnotmerayresultcallback.hpp | 13 ++- .../mwphysics/contacttestresultcallback.cpp | 7 +- .../mwphysics/contacttestresultcallback.hpp | 10 +- .../deepestnotmecontacttestresultcallback.cpp | 47 ---------- .../deepestnotmecontacttestresultcallback.hpp | 34 ------- apps/openmw/mwphysics/movementsolver.cpp | 93 ++++++++++--------- apps/openmw/mwphysics/physicssystem.cpp | 1 - .../mwphysics/projectileconvexcallback.cpp | 18 +--- .../mwphysics/projectileconvexcallback.hpp | 12 ++- 13 files changed, 108 insertions(+), 192 deletions(-) delete mode 100644 apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp delete mode 100644 apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 566aedfff0..53989c1797 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -84,7 +84,7 @@ add_openmw_dir (mwworld add_openmw_dir (mwphysics physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback - contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile + contacttestresultcallback stepper movementsolver projectile actorconvexcallback raycasting mtphysics contacttestwrapper projectileconvexcallback ) diff --git a/apps/openmw/mwphysics/actorconvexcallback.cpp b/apps/openmw/mwphysics/actorconvexcallback.cpp index db077beb31..72bb0eff46 100644 --- a/apps/openmw/mwphysics/actorconvexcallback.cpp +++ b/apps/openmw/mwphysics/actorconvexcallback.cpp @@ -9,35 +9,28 @@ namespace MWPhysics { - class ActorOverlapTester : public btCollisionWorld::ContactResultCallback + namespace { - public: - bool overlapping = false; - - btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0Wrap, int partId0, - int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) override + struct ActorOverlapTester : public btCollisionWorld::ContactResultCallback { - if (cp.getDistance() <= 0.0f) - overlapping = true; - return btScalar(1); - } - }; + bool mOverlapping = false; - ActorConvexCallback::ActorConvexCallback( - const btCollisionObject* me, const btVector3& motion, btScalar minCollisionDot, const btCollisionWorld* world) - : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) - , mMe(me) - , mMotion(motion) - , mMinCollisionDot(minCollisionDot) - , mWorld(world) - { + btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* /*colObj0Wrap*/, + int /*partId0*/, int /*index0*/, const btCollisionObjectWrapper* /*colObj1Wrap*/, int /*partId1*/, + int /*index1*/) override + { + if (cp.getDistance() <= 0.0f) + mOverlapping = true; + return 1; + } + }; } btScalar ActorConvexCallback::addSingleResult( btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { if (convexResult.m_hitCollisionObject == mMe) - return btScalar(1); + return 1; // override data for actor-actor collisions // vanilla Morrowind seems to make overlapping actors collide as though they are both cylinders with a diameter @@ -52,7 +45,7 @@ namespace MWPhysics const_cast(mMe), const_cast(convexResult.m_hitCollisionObject), isOverlapping); - if (isOverlapping.overlapping) + if (isOverlapping.mOverlapping) { auto originA = Misc::Convert::toOsg(mMe->getWorldTransform().getOrigin()); auto originB = Misc::Convert::toOsg(convexResult.m_hitCollisionObject->getWorldTransform().getOrigin()); @@ -73,7 +66,7 @@ namespace MWPhysics } else { - return btScalar(1); + return 1; } } } @@ -82,10 +75,10 @@ namespace MWPhysics { auto* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); if (!projectileHolder->isActive()) - return btScalar(1); + return 1; if (projectileHolder->isValidTarget(mMe)) projectileHolder->hit(mMe, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); - return btScalar(1); + return 1; } btVector3 hitNormalWorld; @@ -101,7 +94,7 @@ namespace MWPhysics // dot product of the motion vector against the collision contact normal btScalar dotCollision = mMotion.dot(hitNormalWorld); if (dotCollision <= mMinCollisionDot) - return btScalar(1); + return 1; return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace); } diff --git a/apps/openmw/mwphysics/actorconvexcallback.hpp b/apps/openmw/mwphysics/actorconvexcallback.hpp index 4b9ab1a8a4..8442097a09 100644 --- a/apps/openmw/mwphysics/actorconvexcallback.hpp +++ b/apps/openmw/mwphysics/actorconvexcallback.hpp @@ -10,8 +10,15 @@ namespace MWPhysics class ActorConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ActorConvexCallback(const btCollisionObject* me, const btVector3& motion, btScalar minCollisionDot, - const btCollisionWorld* world); + explicit ActorConvexCallback(const btCollisionObject* me, const btVector3& motion, btScalar minCollisionDot, + const btCollisionWorld* world) + : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)) + , mMe(me) + , mMotion(motion) + , mMinCollisionDot(minCollisionDot) + , mWorld(world) + { + } btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) override; diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 875704d790..02c94a6f9d 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -1,7 +1,6 @@ #include "closestnotmerayresultcallback.hpp" #include -#include #include @@ -9,14 +8,6 @@ namespace MWPhysics { - ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, - const std::vector& targets, const btVector3& from, const btVector3& to) - : btCollisionWorld::ClosestRayResultCallback(from, to) - , mMe(me) - , mTargets(targets) - { - } - btScalar ClosestNotMeRayResultCallback::addSingleResult( btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) { diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index 37bda3bd52..f7f567ed4e 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MWPHYSICS_CLOSESTNOTMERAYRESULTCALLBACK_H #define OPENMW_MWPHYSICS_CLOSESTNOTMERAYRESULTCALLBACK_H -#include +#include #include @@ -14,14 +14,19 @@ namespace MWPhysics class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector& targets, - const btVector3& from, const btVector3& to); + explicit ClosestNotMeRayResultCallback(const btCollisionObject* me, std::span targets, + const btVector3& from, const btVector3& to) + : btCollisionWorld::ClosestRayResultCallback(from, to) + , mMe(me) + , mTargets(targets) + { + } btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; private: const btCollisionObject* mMe; - const std::vector mTargets; + const std::span mTargets; }; } diff --git a/apps/openmw/mwphysics/contacttestresultcallback.cpp b/apps/openmw/mwphysics/contacttestresultcallback.cpp index dae0a65af0..45d1127de4 100644 --- a/apps/openmw/mwphysics/contacttestresultcallback.cpp +++ b/apps/openmw/mwphysics/contacttestresultcallback.cpp @@ -8,13 +8,8 @@ namespace MWPhysics { - ContactTestResultCallback::ContactTestResultCallback(const btCollisionObject* testedAgainst) - : mTestedAgainst(testedAgainst) - { - } - btScalar ContactTestResultCallback::addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap, - int partId0, int index0, const btCollisionObjectWrapper* col1Wrap, int partId1, int index1) + int /*partId0*/, int /*index0*/, const btCollisionObjectWrapper* col1Wrap, int /*partId1*/, int /*index1*/) { const btCollisionObject* collisionObject = col0Wrap->m_collisionObject; if (collisionObject == mTestedAgainst) diff --git a/apps/openmw/mwphysics/contacttestresultcallback.hpp b/apps/openmw/mwphysics/contacttestresultcallback.hpp index ae900e0208..a9ba06368c 100644 --- a/apps/openmw/mwphysics/contacttestresultcallback.hpp +++ b/apps/openmw/mwphysics/contacttestresultcallback.hpp @@ -14,15 +14,19 @@ namespace MWPhysics { class ContactTestResultCallback : public btCollisionWorld::ContactResultCallback { - const btCollisionObject* mTestedAgainst; - public: - ContactTestResultCallback(const btCollisionObject* testedAgainst); + explicit ContactTestResultCallback(const btCollisionObject* testedAgainst) + : mTestedAgainst(testedAgainst) + { + } btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap, int partId0, int index0, const btCollisionObjectWrapper* col1Wrap, int partId1, int index1) override; std::vector mResult; + + private: + const btCollisionObject* mTestedAgainst; }; } diff --git a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp b/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp deleted file mode 100644 index 766ca79796..0000000000 --- a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "deepestnotmecontacttestresultcallback.hpp" - -#include - -#include - -#include "collisiontype.hpp" - -namespace MWPhysics -{ - - DeepestNotMeContactTestResultCallback::DeepestNotMeContactTestResultCallback( - const btCollisionObject* me, const std::vector& targets, const btVector3& origin) - : mMe(me) - , mTargets(targets) - , mOrigin(origin) - , mLeastDistSqr(std::numeric_limits::max()) - { - } - - btScalar DeepestNotMeContactTestResultCallback::addSingleResult(btManifoldPoint& cp, - const btCollisionObjectWrapper* col0Wrap, int partId0, int index0, const btCollisionObjectWrapper* col1Wrap, - int partId1, int index1) - { - const btCollisionObject* collisionObject = col1Wrap->m_collisionObject; - if (collisionObject != mMe) - { - if (collisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor - && !mTargets.empty()) - { - if ((std::find(mTargets.begin(), mTargets.end(), collisionObject) == mTargets.end())) - return 0.f; - } - - btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); - if (!mObject || distsqr < mLeastDistSqr) - { - mObject = collisionObject; - mLeastDistSqr = distsqr; - mContactPoint = cp.getPositionWorldOnA(); - mContactNormal = cp.m_normalWorldOnB; - } - } - - return 0.f; - } -} diff --git a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp b/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp deleted file mode 100644 index d22a79e643..0000000000 --- a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef OPENMW_MWPHYSICS_DEEPESTNOTMECONTACTTESTRESULTCALLBACK_H -#define OPENMW_MWPHYSICS_DEEPESTNOTMECONTACTTESTRESULTCALLBACK_H - -#include - -#include - -class btCollisionObject; - -namespace MWPhysics -{ - class DeepestNotMeContactTestResultCallback : public btCollisionWorld::ContactResultCallback - { - const btCollisionObject* mMe; - const std::vector mTargets; - - // Store the real origin, since the shape's origin is its center - btVector3 mOrigin; - - public: - const btCollisionObject* mObject{ nullptr }; - btVector3 mContactPoint{ 0, 0, 0 }; - btVector3 mContactNormal{ 0, 0, 0 }; - btScalar mLeastDistSqr; - - DeepestNotMeContactTestResultCallback( - const btCollisionObject* me, const std::vector& targets, const btVector3& origin); - - btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* col0Wrap, int partId0, int index0, - const btCollisionObjectWrapper* col1Wrap, int partId1, int index1) override; - }; -} - -#endif diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index fe5c1a955e..05b9f44654 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -32,53 +32,58 @@ namespace MWPhysics return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor; } - class ContactCollectionCallback : public btCollisionWorld::ContactResultCallback + namespace { - public: - ContactCollectionCallback(const btCollisionObject* me, osg::Vec3f velocity) - : mMe(me) + class ContactCollectionCallback : public btCollisionWorld::ContactResultCallback { - m_collisionFilterGroup = me->getBroadphaseHandle()->m_collisionFilterGroup; - m_collisionFilterMask = me->getBroadphaseHandle()->m_collisionFilterMask & ~CollisionType_Projectile; - mVelocity = Misc::Convert::toBullet(velocity); - } - btScalar addSingleResult(btManifoldPoint& contact, const btCollisionObjectWrapper* colObj0Wrap, int partId0, - int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1) override - { - if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject())) - return 0.0; - // ignore overlap if we're moving in the same direction as it would push us out (don't change this to >=, - // that would break detection when not moving) - if (contact.m_normalWorldOnB.dot(mVelocity) > 0.0) - return 0.0; - auto delta = contact.m_normalWorldOnB * -contact.m_distance1; - mContactSum += delta; - mMaxX = std::max(std::abs(delta.x()), mMaxX); - mMaxY = std::max(std::abs(delta.y()), mMaxY); - mMaxZ = std::max(std::abs(delta.z()), mMaxZ); - if (contact.m_distance1 < mDistance) + public: + explicit ContactCollectionCallback(const btCollisionObject& me, const osg::Vec3f& velocity) + : mVelocity(Misc::Convert::toBullet(velocity)) { - mDistance = contact.m_distance1; - mNormal = contact.m_normalWorldOnB; - mDelta = delta; - return mDistance; + m_collisionFilterGroup = me.getBroadphaseHandle()->m_collisionFilterGroup; + m_collisionFilterMask = me.getBroadphaseHandle()->m_collisionFilterMask & ~CollisionType_Projectile; } - else + + btScalar addSingleResult(btManifoldPoint& contact, const btCollisionObjectWrapper* colObj0Wrap, + int /*partId0*/, int /*index0*/, const btCollisionObjectWrapper* colObj1Wrap, int /*partId1*/, + int /*index1*/) override { - return 0.0; + if (isActor(colObj0Wrap->getCollisionObject()) && isActor(colObj1Wrap->getCollisionObject())) + return 0.0; + // ignore overlap if we're moving in the same direction as it would push us out (don't change this to + // >=, that would break detection when not moving) + if (contact.m_normalWorldOnB.dot(mVelocity) > 0.0) + return 0.0; + auto delta = contact.m_normalWorldOnB * -contact.m_distance1; + mContactSum += delta; + mMaxX = std::max(std::abs(delta.x()), mMaxX); + mMaxY = std::max(std::abs(delta.y()), mMaxY); + mMaxZ = std::max(std::abs(delta.z()), mMaxZ); + if (contact.m_distance1 < mDistance) + { + mDistance = contact.m_distance1; + mNormal = contact.m_normalWorldOnB; + mDelta = delta; + return mDistance; + } + else + { + return 0.0; + } } - } - btScalar mMaxX = 0.0; - btScalar mMaxY = 0.0; - btScalar mMaxZ = 0.0; - btVector3 mContactSum{ 0.0, 0.0, 0.0 }; - btVector3 mNormal{ 0.0, 0.0, 0.0 }; // points towards "me" - btVector3 mDelta{ 0.0, 0.0, 0.0 }; // points towards "me" - btScalar mDistance = 0.0; // negative or zero - protected: - btVector3 mVelocity; - const btCollisionObject* mMe; - }; + + btScalar mMaxX = 0.0; + btScalar mMaxY = 0.0; + btScalar mMaxZ = 0.0; + btVector3 mContactSum{ 0.0, 0.0, 0.0 }; + btVector3 mNormal{ 0.0, 0.0, 0.0 }; // points towards "me" + btVector3 mDelta{ 0.0, 0.0, 0.0 }; // points towards "me" + btScalar mDistance = 0.0; // negative or zero + + protected: + btVector3 mVelocity; + }; + } osg::Vec3f MovementSolver::traceDown(const MWWorld::Ptr& ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight) @@ -454,8 +459,10 @@ namespace MWPhysics if (btFrom == btTo) return; + assert(projectile.mProjectile != nullptr); + ProjectileConvexCallback resultCallback( - projectile.mCaster, projectile.mCollisionObject, btFrom, btTo, projectile.mProjectile); + projectile.mCaster, projectile.mCollisionObject, btFrom, btTo, *projectile.mProjectile); resultCallback.m_collisionFilterMask = CollisionType_AnyPhysical; resultCallback.m_collisionFilterGroup = CollisionType_Projectile; @@ -524,7 +531,7 @@ namespace MWPhysics newTransform.setOrigin(Misc::Convert::toBullet(goodPosition)); actor.mCollisionObject->setWorldTransform(newTransform); - ContactCollectionCallback callback{ actor.mCollisionObject, velocity }; + ContactCollectionCallback callback(*actor.mCollisionObject, velocity); ContactTestWrapper::contactTest( const_cast(collisionWorld), actor.mCollisionObject, callback); return callback; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 149113dfb1..f84f503bb5 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -49,7 +49,6 @@ #include "closestnotmerayresultcallback.hpp" #include "contacttestresultcallback.hpp" -#include "deepestnotmecontacttestresultcallback.hpp" #include "hasspherecollisioncallback.hpp" #include "heightfield.hpp" #include "movementsolver.hpp" diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp index d7e80b4698..913a3edb0c 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.cpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -6,16 +6,6 @@ namespace MWPhysics { - ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, - const btVector3& from, const btVector3& to, Projectile* proj) - : btCollisionWorld::ClosestConvexResultCallback(from, to) - , mCaster(caster) - , mMe(me) - , mProjectile(proj) - { - assert(mProjectile); - } - btScalar ProjectileConvexCallback::addSingleResult( btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) { @@ -33,25 +23,25 @@ namespace MWPhysics { case CollisionType_Actor: { - if (!mProjectile->isValidTarget(hitObject)) + if (!mProjectile.isValidTarget(hitObject)) return 1.f; break; } case CollisionType_Projectile: { auto* target = static_cast(hitObject->getUserPointer()); - if (!mProjectile->isValidTarget(target->getCasterCollisionObject())) + if (!mProjectile.isValidTarget(target->getCasterCollisionObject())) return 1.f; target->hit(mMe, m_hitPointWorld, m_hitNormalWorld); break; } case CollisionType_Water: { - mProjectile->setHitWater(); + mProjectile.setHitWater(); break; } } - mProjectile->hit(hitObject, m_hitPointWorld, m_hitNormalWorld); + mProjectile.hit(hitObject, m_hitPointWorld, m_hitNormalWorld); return result.m_hitFraction; } diff --git a/apps/openmw/mwphysics/projectileconvexcallback.hpp b/apps/openmw/mwphysics/projectileconvexcallback.hpp index 3cd304bab0..d75ace22af 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.hpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.hpp @@ -12,15 +12,21 @@ namespace MWPhysics class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { public: - ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, const btVector3& from, - const btVector3& to, Projectile* proj); + explicit ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, + const btVector3& from, const btVector3& to, Projectile& projectile) + : btCollisionWorld::ClosestConvexResultCallback(from, to) + , mCaster(caster) + , mMe(me) + , mProjectile(projectile) + { + } btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override; private: const btCollisionObject* mCaster; const btCollisionObject* mMe; - Projectile* mProjectile; + Projectile& mProjectile; }; } From c5564323e4431b9632f13847350d398d42644607 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 28 Jan 2024 20:57:46 +0300 Subject: [PATCH 029/451] Correct activation behavior for actors in combat (#7794) Stop battle music upon death animation end --- CHANGELOG.md | 1 + apps/openmw/mwclass/creature.cpp | 9 +++----- apps/openmw/mwclass/npc.cpp | 34 ++++++++++++++++++------------ apps/openmw/mwmechanics/actors.cpp | 3 ++- 4 files changed, 27 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index faea4d0b6c..07d0514f98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -138,6 +138,7 @@ Bug #7770: Sword of the Perithia: Script execution failure Bug #7780: Non-ASCII texture paths in NIF files don't work Bug #7785: OpenMW-CS initialising Skill and Attribute fields to 0 instead of -1 on non-FortifyStat spells + Bug #7794: Fleeing NPCs name tooltip doesn't appear Bug #7796: Absorbed enchantments don't restore magicka Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index d19e5d5c43..2de58c6127 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -474,20 +474,17 @@ 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 && !isInCombat) + // by default user can loot non-fighting actors during death animation + if (Settings::game().mCanLootDuringDeathAnimation) return std::make_unique(ptr); // otherwise wait until death animation if (stats.isDeathAnimationFinished()) return std::make_unique(ptr); } - else if ((!isInCombat || aiSequence.isFleeing()) && !stats.getKnockedDown()) + else if (!stats.getKnockedDown()) return std::make_unique(ptr); // Tribunal and some mod companions oddly enough must use open action as fallback diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b8cd4cd23d..ce1df10db3 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -924,36 +924,43 @@ namespace MWClass } const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + const MWMechanics::AiSequence& aiSequence = stats.getAiSequence(); + const bool isPursuing = aiSequence.isInPursuit() && actor == MWMechanics::getPlayer(); + const bool inCombatWithActor = aiSequence.isInCombat(actor) || isPursuing; if (stats.isDead()) { - // by default user can loot friendly actors during death animation - if (Settings::game().mCanLootDuringDeathAnimation && !stats.getAiSequence().isInCombat()) + // by default user can loot non-fighting actors during death animation + if (Settings::game().mCanLootDuringDeathAnimation) return std::make_unique(ptr); // otherwise wait until death animation if (stats.isDeathAnimationFinished()) return std::make_unique(ptr); } - else if (!stats.getAiSequence().isInCombat()) + else { - if (stats.getKnockedDown() || MWBase::Environment::get().getMechanicsManager()->isSneaking(actor)) - return std::make_unique(ptr); // stealing + const bool allowStealingFromKO + = Settings::game().mAlwaysAllowStealingFromKnockedOutActors || !inCombatWithActor; + if (stats.getKnockedDown() && allowStealingFromKO) + return std::make_unique(ptr); + + const bool allowStealingWhileSneaking = !inCombatWithActor; + if (MWBase::Environment::get().getMechanicsManager()->isSneaking(actor) && allowStealingWhileSneaking) + return std::make_unique(ptr); - // Can't talk to werewolves - if (!getNpcStats(ptr).isWerewolf()) + const bool allowTalking = !inCombatWithActor && !getNpcStats(ptr).isWerewolf(); + if (allowTalking) return std::make_unique(ptr); } - else // In combat - { - if (Settings::game().mAlwaysAllowStealingFromKnockedOutActors && stats.getKnockedDown()) - return std::make_unique(ptr); // stealing - } // Tribunal and some mod companions oddly enough must use open action as fallback if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) return std::make_unique(ptr); + if (inCombatWithActor) + return std::make_unique("#{sActorInCombat}"); + return std::make_unique(); } @@ -1086,7 +1093,8 @@ namespace MWClass if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished()) return true; - if (!customData.mNpcStats.getAiSequence().isInCombat()) + const MWMechanics::AiSequence& aiSeq = customData.mNpcStats.getAiSequence(); + if (!aiSeq.isInCombat() || aiSeq.isFleeing()) return true; if (Settings::game().mAlwaysAllowStealingFromKnockedOutActors && customData.mNpcStats.getKnockedDown()) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d463fa729b..bd55652e5d 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1309,7 +1309,8 @@ namespace MWMechanics if (inProcessingRange) { MWMechanics::CreatureStats& stats = actor.getPtr().getClass().getCreatureStats(actor.getPtr()); - if (!stats.isDead() && stats.getAiSequence().isInCombat()) + bool isDead = stats.isDead() && stats.isDeathAnimationFinished(); + if (!isDead && stats.getAiSequence().isInCombat()) { hasHostiles = true; break; From 0178c5aaef46086333c025c6cd5db76549c7e8bc Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 8 Feb 2024 12:15:57 +0300 Subject: [PATCH 030/451] Remove Open action fallback for Tribunal NPC companions --- apps/openmw/mwclass/npc.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index ce1df10db3..c43a2c1094 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -954,10 +954,6 @@ namespace MWClass return std::make_unique(ptr); } - // Tribunal and some mod companions oddly enough must use open action as fallback - if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) - return std::make_unique(ptr); - if (inCombatWithActor) return std::make_unique("#{sActorInCombat}"); From 1689c59546a862cc5b9ee2276f080bd7a71a7015 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 3 Feb 2024 14:09:50 +0100 Subject: [PATCH 031/451] Add tests for VFS::Path::Normalized --- apps/openmw_test_suite/CMakeLists.txt | 2 + apps/openmw_test_suite/vfs/testpathutil.cpp | 99 +++++++++++++++++++++ components/vfs/pathutil.hpp | 20 +++-- 3 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 apps/openmw_test_suite/vfs/testpathutil.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 967511953d..71da2de590 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -97,6 +97,8 @@ file(GLOB UNITTEST_SRC_FILES esmterrain/testgridsampling.cpp resource/testobjectcache.cpp + + vfs/testpathutil.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/vfs/testpathutil.cpp b/apps/openmw_test_suite/vfs/testpathutil.cpp new file mode 100644 index 0000000000..811b2b3691 --- /dev/null +++ b/apps/openmw_test_suite/vfs/testpathutil.cpp @@ -0,0 +1,99 @@ +#include + +#include + +#include + +namespace VFS::Path +{ + namespace + { + using namespace testing; + + TEST(NormalizedTest, shouldSupportDefaultConstructor) + { + const Normalized value; + EXPECT_EQ(value.value(), ""); + } + + TEST(NormalizedTest, shouldSupportConstructorFromString) + { + const std::string string("Foo\\Bar/baz"); + const Normalized value(string); + EXPECT_EQ(value.value(), "foo/bar/baz"); + } + + TEST(NormalizedTest, shouldSupportConstructorFromConstCharPtr) + { + const char* const ptr = "Foo\\Bar/baz"; + const Normalized value(ptr); + EXPECT_EQ(value.value(), "foo/bar/baz"); + } + + TEST(NormalizedTest, shouldSupportConstructorFromStringView) + { + const std::string_view view = "Foo\\Bar/baz"; + const Normalized value(view); + EXPECT_EQ(value.view(), "foo/bar/baz"); + } + + TEST(NormalizedTest, supportMovingValueOut) + { + Normalized value("Foo\\Bar/baz"); + EXPECT_EQ(std::move(value).value(), "foo/bar/baz"); + EXPECT_EQ(value.value(), ""); + } + + TEST(NormalizedTest, isNotEqualToNotNormalized) + { + const Normalized value("Foo\\Bar/baz"); + EXPECT_NE(value.value(), "Foo\\Bar/baz"); + } + + TEST(NormalizedTest, shouldSupportOperatorLeftShiftToOStream) + { + const Normalized value("Foo\\Bar/baz"); + std::stringstream stream; + stream << value; + EXPECT_EQ(stream.str(), "foo/bar/baz"); + } + + template + struct NormalizedOperatorsTest : Test + { + }; + + TYPED_TEST_SUITE_P(NormalizedOperatorsTest); + + TYPED_TEST_P(NormalizedOperatorsTest, supportsEqual) + { + const Normalized normalized("a/foo/bar/baz"); + const TypeParam otherEqual{ "a/foo/bar/baz" }; + const TypeParam otherNotEqual{ "b/foo/bar/baz" }; + EXPECT_EQ(normalized, otherEqual); + EXPECT_EQ(otherEqual, normalized); + EXPECT_NE(normalized, otherNotEqual); + EXPECT_NE(otherNotEqual, normalized); + } + + TYPED_TEST_P(NormalizedOperatorsTest, supportsLess) + { + const Normalized normalized("b/foo/bar/baz"); + const TypeParam otherEqual{ "b/foo/bar/baz" }; + const TypeParam otherLess{ "a/foo/bar/baz" }; + const TypeParam otherGreater{ "c/foo/bar/baz" }; + EXPECT_FALSE(normalized < otherEqual); + EXPECT_FALSE(otherEqual < normalized); + EXPECT_LT(otherLess, normalized); + EXPECT_FALSE(normalized < otherLess); + EXPECT_LT(normalized, otherGreater); + EXPECT_FALSE(otherGreater < normalized); + } + + REGISTER_TYPED_TEST_SUITE_P(NormalizedOperatorsTest, supportsEqual, supportsLess); + + using StringTypes = Types; + + INSTANTIATE_TYPED_TEST_SUITE_P(Typed, NormalizedOperatorsTest, StringTypes); + } +} diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 0856bfffa2..6ee33f64d2 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -96,22 +96,26 @@ namespace VFS::Path friend bool operator==(const Normalized& lhs, const Normalized& rhs) = default; - template - friend bool operator==(const Normalized& lhs, const T& rhs) + friend bool operator==(const Normalized& lhs, const auto& rhs) { return lhs.mValue == rhs; } + +#if defined(_MSC_VER) && _MSC_VER <= 1935 + friend bool operator==(const auto& lhs, const Normalized& rhs) { - return lhs.mValue == rhs; + return lhs == rhs.mValue; } +#endif - friend bool operator<(const Normalized& lhs, const Normalized& rhs) { return lhs.mValue < rhs.mValue; } + friend bool operator<(const Normalized& lhs, const Normalized& rhs) + { + return lhs.mValue < rhs.mValue; + } - template - friend bool operator<(const Normalized& lhs, const T& rhs) + friend bool operator<(const Normalized& lhs, const auto& rhs) { return lhs.mValue < rhs; } - template - friend bool operator<(const T& lhs, const Normalized& rhs) + friend bool operator<(const auto& lhs, const Normalized& rhs) { return lhs < rhs.mValue; } From 062d3e9c0093c375abae285db2749d140fb1280b Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 3 Feb 2024 14:34:06 +0100 Subject: [PATCH 032/451] Add NormalizedView for normalized paths --- apps/openmw_test_suite/vfs/testpathutil.cpp | 58 ++++++++++++--- components/vfs/pathutil.hpp | 79 +++++++++++++++++++++ 2 files changed, 128 insertions(+), 9 deletions(-) diff --git a/apps/openmw_test_suite/vfs/testpathutil.cpp b/apps/openmw_test_suite/vfs/testpathutil.cpp index 811b2b3691..23a4d46d12 100644 --- a/apps/openmw_test_suite/vfs/testpathutil.cpp +++ b/apps/openmw_test_suite/vfs/testpathutil.cpp @@ -37,6 +37,13 @@ namespace VFS::Path EXPECT_EQ(value.view(), "foo/bar/baz"); } + TEST(NormalizedTest, shouldSupportConstructorFromNormalizedView) + { + const NormalizedView view = "foo/bar/baz"; + const Normalized value(view); + EXPECT_EQ(value.view(), "foo/bar/baz"); + } + TEST(NormalizedTest, supportMovingValueOut) { Normalized value("Foo\\Bar/baz"); @@ -67,9 +74,11 @@ namespace VFS::Path TYPED_TEST_P(NormalizedOperatorsTest, supportsEqual) { - const Normalized normalized("a/foo/bar/baz"); - const TypeParam otherEqual{ "a/foo/bar/baz" }; - const TypeParam otherNotEqual{ "b/foo/bar/baz" }; + using Type0 = typename TypeParam::Type0; + using Type1 = typename TypeParam::Type1; + const Type0 normalized{ "a/foo/bar/baz" }; + const Type1 otherEqual{ "a/foo/bar/baz" }; + const Type1 otherNotEqual{ "b/foo/bar/baz" }; EXPECT_EQ(normalized, otherEqual); EXPECT_EQ(otherEqual, normalized); EXPECT_NE(normalized, otherNotEqual); @@ -78,10 +87,12 @@ namespace VFS::Path TYPED_TEST_P(NormalizedOperatorsTest, supportsLess) { - const Normalized normalized("b/foo/bar/baz"); - const TypeParam otherEqual{ "b/foo/bar/baz" }; - const TypeParam otherLess{ "a/foo/bar/baz" }; - const TypeParam otherGreater{ "c/foo/bar/baz" }; + using Type0 = typename TypeParam::Type0; + using Type1 = typename TypeParam::Type1; + const Type0 normalized{ "b/foo/bar/baz" }; + const Type1 otherEqual{ "b/foo/bar/baz" }; + const Type1 otherLess{ "a/foo/bar/baz" }; + const Type1 otherGreater{ "c/foo/bar/baz" }; EXPECT_FALSE(normalized < otherEqual); EXPECT_FALSE(otherEqual < normalized); EXPECT_LT(otherLess, normalized); @@ -92,8 +103,37 @@ namespace VFS::Path REGISTER_TYPED_TEST_SUITE_P(NormalizedOperatorsTest, supportsEqual, supportsLess); - using StringTypes = Types; + template + struct TypePair + { + using Type0 = T0; + using Type1 = T1; + }; + + using TypePairs = Types, TypePair, + TypePair, TypePair, + TypePair, TypePair, + TypePair, TypePair, + TypePair, TypePair>; + + INSTANTIATE_TYPED_TEST_SUITE_P(Typed, NormalizedOperatorsTest, TypePairs); - INSTANTIATE_TYPED_TEST_SUITE_P(Typed, NormalizedOperatorsTest, StringTypes); + TEST(NormalizedViewTest, shouldSupportConstructorFromNormalized) + { + const Normalized value("Foo\\Bar/baz"); + const NormalizedView view(value); + EXPECT_EQ(view.value(), "foo/bar/baz"); + } + + TEST(NormalizedViewTest, shouldSupportConstexprConstructorFromNormalizedStringLiteral) + { + constexpr NormalizedView view("foo/bar/baz"); + EXPECT_EQ(view.value(), "foo/bar/baz"); + } + + TEST(NormalizedViewTest, constructorShouldThrowExceptionOnNotNormalized) + { + EXPECT_THROW([] { NormalizedView("Foo\\Bar/baz"); }(), std::invalid_argument); + } } } diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 6ee33f64d2..aa7cad8524 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -58,6 +59,59 @@ namespace VFS::Path bool operator()(std::string_view left, std::string_view right) const { return pathLess(left, right); } }; + class Normalized; + + class NormalizedView + { + public: + constexpr NormalizedView() noexcept = default; + + constexpr NormalizedView(const char* value) + : mValue(value) + { + if (!isNormalized(mValue)) + throw std::invalid_argument("NormalizedView value is not normalized: \"" + std::string(mValue) + "\""); + } + + NormalizedView(const Normalized& value) noexcept; + + constexpr std::string_view value() const noexcept { return mValue; } + + friend constexpr bool operator==(const NormalizedView& lhs, const NormalizedView& rhs) = default; + + friend constexpr bool operator==(const NormalizedView& lhs, const auto& rhs) { return lhs.mValue == rhs; } + +#if defined(_MSC_VER) && _MSC_VER <= 1935 + friend constexpr bool operator==(const auto& lhs, const NormalizedView& rhs) + { + return lhs == rhs.mValue; + } +#endif + + friend constexpr bool operator<(const NormalizedView& lhs, const NormalizedView& rhs) + { + return lhs.mValue < rhs.mValue; + } + + friend constexpr bool operator<(const NormalizedView& lhs, const auto& rhs) + { + return lhs.mValue < rhs; + } + + friend constexpr bool operator<(const auto& lhs, const NormalizedView& rhs) + { + return lhs < rhs.mValue; + } + + friend std::ostream& operator<<(std::ostream& stream, const NormalizedView& value) + { + return stream << value.mValue; + } + + private: + std::string_view mValue; + }; + class Normalized { public: @@ -84,6 +138,11 @@ namespace VFS::Path normalizeFilenameInPlace(mValue); } + explicit Normalized(NormalizedView value) + : mValue(value.value()) + { + } + const std::string& value() const& { return mValue; } std::string value() && { return std::move(mValue); } @@ -105,6 +164,11 @@ namespace VFS::Path } #endif + friend bool operator==(const Normalized& lhs, const NormalizedView& rhs) + { + return lhs.mValue == rhs.value(); + } + friend bool operator<(const Normalized& lhs, const Normalized& rhs) { return lhs.mValue < rhs.mValue; @@ -120,6 +184,16 @@ namespace VFS::Path return lhs < rhs.mValue; } + friend bool operator<(const Normalized& lhs, const NormalizedView& rhs) + { + return lhs.mValue < rhs.value(); + } + + friend bool operator<(const NormalizedView& lhs, const Normalized& rhs) + { + return lhs.value() < rhs.mValue; + } + friend std::ostream& operator<<(std::ostream& stream, const Normalized& value) { return stream << value.mValue; @@ -128,6 +202,11 @@ namespace VFS::Path private: std::string mValue; }; + + inline NormalizedView::NormalizedView(const Normalized& value) noexcept + : mValue(value.view()) + { + } } #endif From a6657c18cc82a5fe1c03fe4175601fcd8029228c Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 18 Jan 2024 00:13:34 +0100 Subject: [PATCH 033/451] Use normalized path for file archives indices --- apps/openmw_test_suite/testing_util.hpp | 14 +++++--------- components/vfs/archive.hpp | 4 ++-- components/vfs/bsaarchive.hpp | 11 +++-------- components/vfs/filesystemarchive.cpp | 6 +++--- components/vfs/filesystemarchive.hpp | 4 ++-- 5 files changed, 15 insertions(+), 24 deletions(-) diff --git a/apps/openmw_test_suite/testing_util.hpp b/apps/openmw_test_suite/testing_util.hpp index b819848a8f..ad1b0423ef 100644 --- a/apps/openmw_test_suite/testing_util.hpp +++ b/apps/openmw_test_suite/testing_util.hpp @@ -51,25 +51,21 @@ namespace TestingOpenMW struct VFSTestData : public VFS::Archive { - std::map mFiles; + VFS::FileMap mFiles; - VFSTestData(std::map files) + explicit VFSTestData(VFS::FileMap&& files) : mFiles(std::move(files)) { } - void listResources(VFS::FileMap& out) override - { - for (const auto& [key, value] : mFiles) - out.emplace(key, value); - } + void listResources(VFS::FileMap& out) override { out = mFiles; } - bool contains(std::string_view file) const override { return mFiles.contains(file); } + bool contains(VFS::Path::NormalizedView file) const override { return mFiles.contains(file); } std::string getDescription() const override { return "TestData"; } }; - inline std::unique_ptr createTestVFS(std::map files) + inline std::unique_ptr createTestVFS(VFS::FileMap&& files) { auto vfs = std::make_unique(); vfs->addArchive(std::make_unique(std::move(files))); diff --git a/components/vfs/archive.hpp b/components/vfs/archive.hpp index 42b88219d7..bd793b8523 100644 --- a/components/vfs/archive.hpp +++ b/components/vfs/archive.hpp @@ -2,9 +2,9 @@ #define OPENMW_COMPONENTS_VFS_ARCHIVE_H #include -#include #include "filemap.hpp" +#include "pathutil.hpp" namespace VFS { @@ -17,7 +17,7 @@ namespace VFS virtual void listResources(FileMap& out) = 0; /// True if this archive contains the provided normalized file. - virtual bool contains(std::string_view file) const = 0; + virtual bool contains(Path::NormalizedView file) const = 0; virtual std::string getDescription() const = 0; }; diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 304fc438ad..847aeca509 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -52,19 +52,14 @@ namespace VFS void listResources(FileMap& out) override { for (auto& resource : mResources) - { - std::string ent = resource.mInfo->name(); - Path::normalizeFilenameInPlace(ent); - - out[ent] = &resource; - } + out[VFS::Path::Normalized(resource.mInfo->name())] = &resource; } - bool contains(std::string_view file) const override + bool contains(Path::NormalizedView file) const override { for (const auto& it : mResources) { - if (Path::pathEqual(file, it.mInfo->name())) + if (Path::pathEqual(file.value(), it.mInfo->name())) return true; } return false; diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index 7d88dd9cc0..c72798e7ea 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -37,9 +37,9 @@ namespace VFS FileSystemArchiveFile file(path); - std::string searchable = Path::normalizeFilename(std::string_view{ proper }.substr(prefix)); + VFS::Path::Normalized searchable(std::string_view{ proper }.substr(prefix)); - const auto inserted = mIndex.emplace(searchable, file); + const auto inserted = mIndex.emplace(std::move(searchable), std::move(file)); if (!inserted.second) Log(Debug::Warning) << "Warning: found duplicate file for '" << proper @@ -56,7 +56,7 @@ namespace VFS } } - bool FileSystemArchive::contains(std::string_view file) const + bool FileSystemArchive::contains(Path::NormalizedView file) const { return mIndex.find(file) != mIndex.end(); } diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index 00fe5ba971..b158ef3472 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -30,12 +30,12 @@ namespace VFS void listResources(FileMap& out) override; - bool contains(std::string_view file) const override; + bool contains(Path::NormalizedView file) const override; std::string getDescription() const override; private: - std::map> mIndex; + std::map> mIndex; bool mBuiltIndex; std::filesystem::path mPath; }; From 525dee00f11edf8500092ef19dde9f8accb26a38 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 6 Feb 2024 10:58:40 +0400 Subject: [PATCH 034/451] Refraction fog based on water depth (feature 5926) --- AUTHORS.md | 1 + CHANGELOG.md | 1 + apps/openmw/mwrender/water.cpp | 5 ++++- files/shaders/compatibility/water.frag | 22 ++++++++++++++++++++-- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index e2903febe4..8873113da2 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -187,6 +187,7 @@ Programmers pkubik PLkolek PlutonicOverkill + Qlonever Radu-Marius Popovici (rpopovici) Rafael Moura (dhustkoder) Randy Davin (Kindi) diff --git a/CHANGELOG.md b/CHANGELOG.md index faea4d0b6c..2599db623d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,6 +143,7 @@ Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty Feature #5492: Let rain and snow collide with statics + Feature #5926: Refraction based on water depth Feature #6149: Dehardcode Lua API_REVISION Feature #6152: Playing music via lua scripts Feature #6188: Specular lighting from point light sources diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index d5fb01242f..9fdb0583a2 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -114,7 +114,10 @@ namespace MWRender } // move the plane back along its normal a little bit to prevent bleeding at the water shore - const float clipFudge = -5; + float fov = Settings::camera().mFieldOfView; + const float clipFudgeMin = 2.5; // minimum offset of clip plane + const float clipFudgeScale = -15000.0; + float clipFudge = abs(abs((*mCullPlane)[3]) - eyePoint.z()) * fov / clipFudgeScale - clipFudgeMin; modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * clipFudge); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 5817b0c5ae..c971f92b99 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -17,6 +17,8 @@ // tweakables -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- const float VISIBILITY = 2500.0; +const float VISIBILITY_DEPTH = VISIBILITY * 1.5; +const float DEPTH_FADE = 0.15; const float BIG_WAVES_X = 0.1; // strength of big waves const float BIG_WAVES_Y = 0.1; @@ -48,6 +50,7 @@ const float SUN_SPEC_FADING_THRESHOLD = 0.15; // visibility at which sun s const float SPEC_HARDNESS = 256.0; // specular highlights hardness const float BUMP_SUPPRESS_DEPTH = 300.0; // at what water depth bumpmap will be suppressed for reflections and refractions (prevents artifacts at shores) +const float REFR_FOG_DISTORT_DISTANCE = 3000.0; // at what distance refraction fog will be calculated using real water depth instead of distorted depth (prevents splotchy shores) const vec2 WIND_DIR = vec2(0.5f, -0.8f); const float WIND_SPEED = 0.2f; @@ -160,9 +163,10 @@ void main(void) vec2 screenCoordsOffset = normal.xy * REFL_BUMP; #if REFRACTION float depthSample = linearizeDepth(sampleRefractionDepthMap(screenCoords), near, far) * radialise; - float depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords-screenCoordsOffset), near, far) * radialise; float surfaceDepth = linearizeDepth(gl_FragCoord.z, near, far) * radialise; float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum + float depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far) * radialise; + float waterDepthDistorted = max(depthSampleDistorted - surfaceDepth, 0.0); screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); #endif // reflection @@ -185,6 +189,16 @@ void main(void) // no alpha here, so make sure raindrop ripple specularity gets properly subdued rainSpecular *= clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); + // selectively nullify screenCoordsOffset to eliminate remaining shore artifacts, not needed for reflection + if (cameraPos.z > 0.0 && realWaterDepth <= VISIBILITY_DEPTH && waterDepthDistorted > VISIBILITY_DEPTH) + screenCoordsOffset = vec2(0.0); + + depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far) * radialise; + waterDepthDistorted = max(depthSampleDistorted - surfaceDepth, 0.0); + + // fade to realWaterDepth at a distance to compensate for physically inaccurate depth calculation + waterDepthDistorted = mix(waterDepthDistorted, realWaterDepth, min(surfaceDepth / REFR_FOG_DISTORT_DISTANCE, 1.0)); + // refraction vec3 refraction = sampleRefractionMap(screenCoords - screenCoordsOffset).rgb; vec3 rawRefraction = refraction; @@ -193,7 +207,11 @@ void main(void) if (cameraPos.z < 0.0) refraction = clamp(refraction * 1.5, 0.0, 1.0); else - refraction = mix(refraction, waterColor, clamp(depthSampleDistorted/VISIBILITY, 0.0, 1.0)); + { + float depthCorrection = sqrt(1.0 + 4.0 * DEPTH_FADE * DEPTH_FADE); + float factor = DEPTH_FADE * DEPTH_FADE / (-0.5 * depthCorrection + 0.5 - waterDepthDistorted / VISIBILITY) + 0.5 * depthCorrection + 0.5; + refraction = mix(refraction, waterColor, clamp(factor, 0.0, 1.0)); + } // sunlight scattering // normal for sunlight scattering From 7586acc18bf2dbd05b65165c7f9b5118d25fb032 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 8 Feb 2024 16:53:48 +0100 Subject: [PATCH 035/451] Remove Core from functions that aren't in openmw.core --- apps/openmw/mwlua/birthsignbindings.cpp | 2 +- apps/openmw/mwlua/birthsignbindings.hpp | 2 +- apps/openmw/mwlua/classbindings.cpp | 2 +- apps/openmw/mwlua/classbindings.hpp | 2 +- apps/openmw/mwlua/types/npc.cpp | 2 +- apps/openmw/mwlua/types/player.cpp | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp index 5592179fee..e569bc1b8f 100644 --- a/apps/openmw/mwlua/birthsignbindings.cpp +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -25,7 +25,7 @@ namespace sol namespace MWLua { - sol::table initCoreBirthSignBindings(const Context& context) + sol::table initBirthSignRecordBindings(const Context& context) { sol::state_view& lua = context.mLua->sol(); sol::table birthSigns(context.mLua->sol(), sol::create); diff --git a/apps/openmw/mwlua/birthsignbindings.hpp b/apps/openmw/mwlua/birthsignbindings.hpp index 7c88b8cccb..bf41707d47 100644 --- a/apps/openmw/mwlua/birthsignbindings.hpp +++ b/apps/openmw/mwlua/birthsignbindings.hpp @@ -7,7 +7,7 @@ namespace MWLua { - sol::table initCoreBirthSignBindings(const Context& context); + sol::table initBirthSignRecordBindings(const Context& context); } #endif // MWLUA_BIRTHSIGNBINDINGS_H diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index 339b724f19..ea1ea8e7ef 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -25,7 +25,7 @@ namespace sol namespace MWLua { - sol::table initCoreClassBindings(const Context& context) + sol::table initClassRecordBindings(const Context& context) { sol::state_view& lua = context.mLua->sol(); sol::table classes(context.mLua->sol(), sol::create); diff --git a/apps/openmw/mwlua/classbindings.hpp b/apps/openmw/mwlua/classbindings.hpp index 9dd9befae4..1acb0a9ad3 100644 --- a/apps/openmw/mwlua/classbindings.hpp +++ b/apps/openmw/mwlua/classbindings.hpp @@ -7,7 +7,7 @@ namespace MWLua { - sol::table initCoreClassBindings(const Context& context); + sol::table initClassRecordBindings(const Context& context); } #endif // MWLUA_CLASSBINDINGS_H diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index b1ac3d994a..d7d459bb81 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -85,7 +85,7 @@ namespace MWLua record["baseGold"] = sol::readonly_property([](const ESM::NPC& rec) -> int { return rec.mNpdt.mGold; }); addActorServicesBindings(record, context); - npc["classes"] = initCoreClassBindings(context); + npc["classes"] = initClassRecordBindings(context); // This function is game-specific, in future we should replace it with something more universal. npc["isWerewolf"] = [](const Object& o) { diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index c12f40e832..130d3ded21 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -190,7 +190,7 @@ namespace MWLua return MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1; }; - player["birthSigns"] = initCoreBirthSignBindings(context); + player["birthSigns"] = initBirthSignRecordBindings(context); player["getBirthSign"] = [](const Object& player) -> std::string { verifyPlayer(player); return MWBase::Environment::get().getWorld()->getPlayer().getBirthSign().serializeText(); From 86666761a3c037b399ae1b6e522863f444184d06 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 8 Feb 2024 21:51:54 -0600 Subject: [PATCH 036/451] Requested changes --- apps/openmw/mwlua/mwscriptbindings.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index ded00dc2b2..c04339f28a 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -53,7 +53,7 @@ namespace sol namespace MWLua { - auto getGlobalVariableValue(const std::string_view globalId) -> float + float getGlobalVariableValue(const std::string_view globalId) { char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); if (varType == 'f') @@ -147,7 +147,7 @@ namespace MWLua return getGlobalVariableValue(globalId); }, [](const GlobalStore& store, int index) -> sol::optional { - if (index < 1 || index >= store.getSize()) + if (index < 1 || store.getSize() < index) return sol::nullopt; auto g = store.at(index - 1); if (g == nullptr) @@ -170,7 +170,7 @@ namespace MWLua if (index >= store.getSize()) return sol::nullopt; - const auto& global = store.at(index++); + const ESM::Global* global = store.at(index++); if (!global) return sol::nullopt; From 38ab09a52eb8d80c0da0729a916c257e60df6346 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 7 Feb 2024 07:06:24 +0300 Subject: [PATCH 037/451] Try to uncursify DebugDrawer scene representation --- apps/openmw/mwrender/renderingmanager.cpp | 5 +- apps/openmw/mwrender/renderingmanager.hpp | 2 +- components/debug/debugdraw.cpp | 77 +++++++++++------------ components/debug/debugdraw.hpp | 18 +++--- components/sceneutil/serialize.cpp | 25 ++++---- 5 files changed, 66 insertions(+), 61 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 15faabb6df..978fe47f44 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -432,8 +432,9 @@ namespace MWRender mViewer->getIncrementalCompileOperation()->setTargetFrameRate(Settings::cells().mTargetFramerate); } - mDebugDraw - = std::make_unique(mResourceSystem->getSceneManager()->getShaderManager(), mRootNode); + mDebugDraw = new Debug::DebugDrawer(mResourceSystem->getSceneManager()->getShaderManager()); + mRootNode->addChild(mDebugDraw); + mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); mEffectManager = std::make_unique(sceneRoot, mResourceSystem); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 22ef987c01..8f85ce6a3f 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -335,7 +335,7 @@ namespace MWRender osg::ref_ptr mPlayerAnimation; osg::ref_ptr mPlayerNode; std::unique_ptr mCamera; - std::unique_ptr mDebugDraw; + osg::ref_ptr mDebugDraw; osg::ref_ptr mStateUpdater; osg::ref_ptr mSharedUniformStateUpdater; diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index 32f71580a8..b83829facd 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -215,12 +215,12 @@ static void generateCylinder(osg::Geometry& geom, float radius, float height, in geom.addPrimitiveSet(indices); } -static int getIdexBufferReadFromFrame(const long long int& nFrame) +static int getIndexBufferReadFromFrame(const unsigned int& nFrame) { return nFrame % 2; } -static int getIdexBufferWriteFromFrame(const long long int& nFrame) +static int getIndexBufferWriteFromFrame(const unsigned int& nFrame) { return (nFrame + 1) % 2; } @@ -248,6 +248,16 @@ namespace Debug makeLineInstance(*mLinesToDraw); } + DebugCustomDraw::DebugCustomDraw(const DebugCustomDraw& copy, const osg::CopyOp& copyop) + : Drawable(copy, copyop) + , mShapesToDraw(copy.mShapesToDraw) + , mLinesToDraw(copy.mLinesToDraw) + , mCubeGeometry(copy.mCubeGeometry) + , mCylinderGeometry(copy.mCylinderGeometry) + , mWireCubeGeometry(copy.mWireCubeGeometry) + { + } + void DebugCustomDraw::drawImplementation(osg::RenderInfo& renderInfo) const { auto state = renderInfo.getState(); @@ -308,43 +318,23 @@ namespace Debug static_cast(mLinesToDraw->getVertexArray())->clear(); static_cast(mLinesToDraw->getNormalArray())->clear(); } +} - class DebugDrawCallback : public SceneUtil::NodeCallback - { - public: - DebugDrawCallback(Debug::DebugDrawer& debugDrawer) - : mDebugDrawer(debugDrawer) - { - } - - void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - mDebugDrawer.mCurrentFrame = nv->getTraversalNumber(); - int indexRead = getIdexBufferReadFromFrame(mDebugDrawer.mCurrentFrame); - auto& lines = mDebugDrawer.mCustomDebugDrawer[indexRead]->mLinesToDraw; - lines->removePrimitiveSet(0, 1); - lines->addPrimitiveSet(new osg::DrawArrays( - osg::PrimitiveSet::LINES, 0, static_cast(lines->getVertexArray())->size())); - - nv->pushOntoNodePath(mDebugDrawer.mCustomDebugDrawer[indexRead]); - nv->apply(*mDebugDrawer.mCustomDebugDrawer[indexRead]); - nv->popFromNodePath(); - } - - Debug::DebugDrawer& mDebugDrawer; - }; +Debug::DebugDrawer::DebugDrawer(const DebugDrawer& copy, const osg::CopyOp& copyop) + : Drawable(copy, copyop) + , mCurrentFrame(copy.mCurrentFrame) + , mCustomDebugDrawer(copy.mCustomDebugDrawer) +{ } -Debug::DebugDrawer::DebugDrawer(Shader::ShaderManager& shaderManager, osg::ref_ptr parentNode) - : mParentNode(std::move(parentNode)) +Debug::DebugDrawer::DebugDrawer(Shader::ShaderManager& shaderManager) { mCurrentFrame = 0; auto program = shaderManager.getProgram("debug"); - mDebugDrawSceneObjects = new osg::Group; - mDebugDrawSceneObjects->setCullingActive(false); - osg::StateSet* stateset = mDebugDrawSceneObjects->getOrCreateStateSet(); + setCullingActive(false); + osg::StateSet* stateset = getOrCreateStateSet(); stateset->addUniform(new osg::Uniform("color", osg::Vec3f(1., 1., 1.))); stateset->addUniform(new osg::Uniform("trans", osg::Vec3f(0., 0., 0.))); stateset->addUniform(new osg::Uniform("scale", osg::Vec3f(1., 1., 1.))); @@ -378,19 +368,28 @@ Debug::DebugDrawer::DebugDrawer(Shader::ShaderManager& shaderManager, osg::ref_p mCustomDebugDrawer[i]->mCubeGeometry = cubeGeometry; mCustomDebugDrawer[i]->mCylinderGeometry = cylinderGeom; } - mDebugDrawSceneObjects->addCullCallback(new DebugDrawCallback(*this)); - - mParentNode->addChild(mDebugDrawSceneObjects); } -Debug::DebugDrawer::~DebugDrawer() +void Debug::DebugDrawer::accept(osg::NodeVisitor& nv) { - mParentNode->removeChild(mDebugDrawSceneObjects); + if (!nv.validNodeMask(*this)) + return; + + mCurrentFrame = nv.getTraversalNumber(); + int indexRead = getIndexBufferReadFromFrame(mCurrentFrame); + auto& lines = mCustomDebugDrawer[indexRead]->mLinesToDraw; + lines->removePrimitiveSet(0, 1); + lines->addPrimitiveSet(new osg::DrawArrays( + osg::PrimitiveSet::LINES, 0, static_cast(lines->getVertexArray())->size())); + + nv.pushOntoNodePath(this); + mCustomDebugDrawer[indexRead]->accept(nv); + nv.popFromNodePath(); } void Debug::DebugDrawer::drawCube(osg::Vec3f mPosition, osg::Vec3f mDims, osg::Vec3f mColor) { - mCustomDebugDrawer[getIdexBufferWriteFromFrame(mCurrentFrame)]->mShapesToDraw.push_back( + mCustomDebugDrawer[getIndexBufferWriteFromFrame(mCurrentFrame)]->mShapesToDraw.push_back( { mPosition, mDims, mColor, DrawShape::Cube }); } @@ -403,12 +402,12 @@ void Debug::DebugDrawer::drawCubeMinMax(osg::Vec3f min, osg::Vec3f max, osg::Vec void Debug::DebugDrawer::addDrawCall(const DrawCall& draw) { - mCustomDebugDrawer[getIdexBufferWriteFromFrame(mCurrentFrame)]->mShapesToDraw.push_back(draw); + mCustomDebugDrawer[getIndexBufferWriteFromFrame(mCurrentFrame)]->mShapesToDraw.push_back(draw); } void Debug::DebugDrawer::addLine(const osg::Vec3& start, const osg::Vec3& end, const osg::Vec3 color) { - const int indexWrite = getIdexBufferWriteFromFrame(mCurrentFrame); + const int indexWrite = getIndexBufferWriteFromFrame(mCurrentFrame); const auto& lines = mCustomDebugDrawer[indexWrite]->mLinesToDraw; auto vertices = static_cast(lines->getVertexArray()); auto colors = static_cast(lines->getNormalArray()); diff --git a/components/debug/debugdraw.hpp b/components/debug/debugdraw.hpp index 659968d35a..610c89d656 100644 --- a/components/debug/debugdraw.hpp +++ b/components/debug/debugdraw.hpp @@ -70,6 +70,9 @@ namespace Debug { public: DebugCustomDraw(); + DebugCustomDraw(const DebugCustomDraw& copy, const osg::CopyOp& copyop); + + META_Object(Debug, DebugCustomDraw) mutable std::vector mShapesToDraw; osg::ref_ptr mLinesToDraw; @@ -81,12 +84,15 @@ namespace Debug virtual void drawImplementation(osg::RenderInfo&) const override; }; - struct DebugDrawer + struct DebugDrawer : public osg::Drawable { - friend DebugDrawCallback; + DebugDrawer() = default; + DebugDrawer(const DebugDrawer& copy, const osg::CopyOp& copyop); + DebugDrawer(Shader::ShaderManager& shaderManager); + + META_Object(Debug, DebugDrawer) - DebugDrawer(Shader::ShaderManager& shaderManager, osg::ref_ptr parentNode); - ~DebugDrawer(); + void accept(osg::NodeVisitor& nv) override; void drawCube( osg::Vec3f mPosition, osg::Vec3f mDims = osg::Vec3(50., 50., 50.), osg::Vec3f mColor = colorWhite); @@ -95,11 +101,9 @@ namespace Debug void addLine(const osg::Vec3& start, const osg::Vec3& end, const osg::Vec3 color = colorWhite); private: - long long int mCurrentFrame; + unsigned int mCurrentFrame; std::array, 2> mCustomDebugDrawer; - osg::ref_ptr mDebugDrawSceneObjects; - osg::ref_ptr mParentNode; }; } #endif // ! diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 8d8acacae4..fa239e692f 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -175,18 +175,19 @@ namespace SceneUtil mgr->addWrapper(new GeometrySerializer); // ignore the below for now to avoid warning spam - const char* ignore[] = { "MWRender::PtrHolder", "Resource::TemplateRef", "Resource::TemplateMultiRef", - "SceneUtil::CompositeStateSetUpdater", "SceneUtil::UBOManager", "SceneUtil::LightListCallback", - "SceneUtil::LightManagerUpdateCallback", "SceneUtil::FFPLightStateAttribute", - "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", - "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", - "Shader::AddedState", "Shader::RemovedAlphaFunc", "NifOsg::FlipController", - "NifOsg::KeyframeController", "NifOsg::Emitter", "NifOsg::ParticleColorAffector", - "NifOsg::ParticleSystem", "NifOsg::GravityAffector", "NifOsg::ParticleBomb", "NifOsg::GrowFadeAffector", - "NifOsg::InverseWorldMatrix", "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", - "NifOsg::UpdateMorphGeometry", "NifOsg::UVController", "NifOsg::VisController", "osgMyGUI::Drawable", - "osg::DrawCallback", "osg::UniformBufferObject", "osgOQ::ClearQueriesCallback", - "osgOQ::RetrieveQueriesCallback", "osg::DummyObject" }; + const char* ignore[] + = { "Debug::DebugDrawer", "MWRender::PtrHolder", "Resource::TemplateRef", "Resource::TemplateMultiRef", + "SceneUtil::CompositeStateSetUpdater", "SceneUtil::UBOManager", "SceneUtil::LightListCallback", + "SceneUtil::LightManagerUpdateCallback", "SceneUtil::FFPLightStateAttribute", + "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", + "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", + "Shader::AddedState", "Shader::RemovedAlphaFunc", "NifOsg::FlipController", + "NifOsg::KeyframeController", "NifOsg::Emitter", "NifOsg::ParticleColorAffector", + "NifOsg::ParticleSystem", "NifOsg::GravityAffector", "NifOsg::ParticleBomb", + "NifOsg::GrowFadeAffector", "NifOsg::InverseWorldMatrix", "NifOsg::StaticBoundingBoxCallback", + "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", "NifOsg::UVController", + "NifOsg::VisController", "osgMyGUI::Drawable", "osg::DrawCallback", "osg::UniformBufferObject", + "osgOQ::ClearQueriesCallback", "osgOQ::RetrieveQueriesCallback", "osg::DummyObject" }; for (size_t i = 0; i < sizeof(ignore) / sizeof(ignore[0]); ++i) { mgr->addWrapper(makeDummySerializer(ignore[i])); From f9498e6ea4624b65d323c35b9a0d7513b0a98718 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 7 Feb 2024 09:14:17 +0300 Subject: [PATCH 038/451] Make DebugDrawer a LightManager child, don't use VAO for lines Fixes terrain lighting but currently breaks non-line primitive rendering in exteriors --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- components/debug/debugdraw.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 978fe47f44..799e4af24d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -433,7 +433,7 @@ namespace MWRender } mDebugDraw = new Debug::DebugDrawer(mResourceSystem->getSceneManager()->getShaderManager()); - mRootNode->addChild(mDebugDraw); + sceneRoot->addChild(mDebugDraw); mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index b83829facd..37584cd950 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -232,7 +232,7 @@ namespace Debug auto vertices = new osg::Vec3Array; auto color = new osg::Vec3Array; lines.setDataVariance(osg::Object::STATIC); - lines.setUseVertexArrayObject(true); + lines.setUseVertexBufferObjects(true); lines.setUseDisplayList(false); lines.setCullingActive(false); From 0d1da08493eb4d9cc2e6c9d5970b7ec1e66db4dc Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 9 Feb 2024 20:47:08 +0300 Subject: [PATCH 039/451] Set node mask on DebugDrawer Fixes primitive drawing in exteriors/quasiexteriors --- apps/openmw/mwrender/renderingmanager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 799e4af24d..62e910806f 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -433,6 +433,7 @@ namespace MWRender } mDebugDraw = new Debug::DebugDrawer(mResourceSystem->getSceneManager()->getShaderManager()); + mDebugDraw->setNodeMask(Mask_Debug); sceneRoot->addChild(mDebugDraw); mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); From 9531b6983a8c3295b0db851bef52dbac6aa80d1f Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 9 Feb 2024 21:39:02 +0300 Subject: [PATCH 040/451] Don't reallocate debug line primitives --- components/debug/debugdraw.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index 37584cd950..c757d4c364 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -317,6 +317,7 @@ namespace Debug mShapesToDraw.clear(); static_cast(mLinesToDraw->getVertexArray())->clear(); static_cast(mLinesToDraw->getNormalArray())->clear(); + static_cast(mLinesToDraw->getPrimitiveSet(0))->setCount(0); } } @@ -377,10 +378,6 @@ void Debug::DebugDrawer::accept(osg::NodeVisitor& nv) mCurrentFrame = nv.getTraversalNumber(); int indexRead = getIndexBufferReadFromFrame(mCurrentFrame); - auto& lines = mCustomDebugDrawer[indexRead]->mLinesToDraw; - lines->removePrimitiveSet(0, 1); - lines->addPrimitiveSet(new osg::DrawArrays( - osg::PrimitiveSet::LINES, 0, static_cast(lines->getVertexArray())->size())); nv.pushOntoNodePath(this); mCustomDebugDrawer[indexRead]->accept(nv); @@ -411,6 +408,7 @@ void Debug::DebugDrawer::addLine(const osg::Vec3& start, const osg::Vec3& end, c const auto& lines = mCustomDebugDrawer[indexWrite]->mLinesToDraw; auto vertices = static_cast(lines->getVertexArray()); auto colors = static_cast(lines->getNormalArray()); + auto primitive = static_cast(lines->getPrimitiveSet(0)); vertices->push_back(start); vertices->push_back(end); @@ -419,4 +417,6 @@ void Debug::DebugDrawer::addLine(const osg::Vec3& start, const osg::Vec3& end, c colors->push_back(color); colors->push_back(color); colors->dirty(); + + primitive->setCount(vertices->size()); } From 6a96cdaa313daa8f58b0f4c313a2a9277c8aaaa9 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 9 Feb 2024 21:51:58 +0300 Subject: [PATCH 041/451] Make DebugDrawer a Node --- components/debug/debugdraw.cpp | 13 +++---------- components/debug/debugdraw.hpp | 6 +++--- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index c757d4c364..bbbf2c238e 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -322,7 +322,7 @@ namespace Debug } Debug::DebugDrawer::DebugDrawer(const DebugDrawer& copy, const osg::CopyOp& copyop) - : Drawable(copy, copyop) + : Node(copy, copyop) , mCurrentFrame(copy.mCurrentFrame) , mCustomDebugDrawer(copy.mCustomDebugDrawer) { @@ -371,17 +371,10 @@ Debug::DebugDrawer::DebugDrawer(Shader::ShaderManager& shaderManager) } } -void Debug::DebugDrawer::accept(osg::NodeVisitor& nv) +void Debug::DebugDrawer::traverse(osg::NodeVisitor& nv) { - if (!nv.validNodeMask(*this)) - return; - mCurrentFrame = nv.getTraversalNumber(); - int indexRead = getIndexBufferReadFromFrame(mCurrentFrame); - - nv.pushOntoNodePath(this); - mCustomDebugDrawer[indexRead]->accept(nv); - nv.popFromNodePath(); + mCustomDebugDrawer[getIndexBufferReadFromFrame(mCurrentFrame)]->accept(nv); } void Debug::DebugDrawer::drawCube(osg::Vec3f mPosition, osg::Vec3f mDims, osg::Vec3f mColor) diff --git a/components/debug/debugdraw.hpp b/components/debug/debugdraw.hpp index 610c89d656..7d7c975749 100644 --- a/components/debug/debugdraw.hpp +++ b/components/debug/debugdraw.hpp @@ -84,15 +84,15 @@ namespace Debug virtual void drawImplementation(osg::RenderInfo&) const override; }; - struct DebugDrawer : public osg::Drawable + struct DebugDrawer : public osg::Node { DebugDrawer() = default; DebugDrawer(const DebugDrawer& copy, const osg::CopyOp& copyop); DebugDrawer(Shader::ShaderManager& shaderManager); - META_Object(Debug, DebugDrawer) + META_Node(Debug, DebugDrawer) - void accept(osg::NodeVisitor& nv) override; + void traverse(osg::NodeVisitor& nv) override; void drawCube( osg::Vec3f mPosition, osg::Vec3f mDims = osg::Vec3(50., 50., 50.), osg::Vec3f mColor = colorWhite); From 4df62d53db442c9770c04a6f1ee634c0a2f2b438 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 10 Feb 2024 01:25:15 +0300 Subject: [PATCH 042/451] Fix OSG boilerplate macro for DebugCustomDraw --- components/debug/debugdraw.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/debug/debugdraw.hpp b/components/debug/debugdraw.hpp index 7d7c975749..2518813cad 100644 --- a/components/debug/debugdraw.hpp +++ b/components/debug/debugdraw.hpp @@ -72,7 +72,7 @@ namespace Debug DebugCustomDraw(); DebugCustomDraw(const DebugCustomDraw& copy, const osg::CopyOp& copyop); - META_Object(Debug, DebugCustomDraw) + META_Node(Debug, DebugCustomDraw) mutable std::vector mShapesToDraw; osg::ref_ptr mLinesToDraw; From c68dee214ee20b11ad03a7b4a50b15c7dc156246 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 10 Feb 2024 22:53:29 +0100 Subject: [PATCH 043/451] Mouse input engine handlers --- apps/openmw/mwbase/luamanager.hpp | 11 ++++++++++- apps/openmw/mwinput/mousemanager.cpp | 11 +++++++++++ apps/openmw/mwlua/inputprocessor.hpp | 15 ++++++++++++++- .../reference/lua-scripting/engine_handlers.rst | 9 +++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index e865756408..69693d47a2 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -81,6 +81,12 @@ namespace MWBase struct InputEvent { + struct WheelChange + { + int x; + int y; + }; + enum { KeyPressed, @@ -91,8 +97,11 @@ namespace MWBase TouchPressed, TouchReleased, TouchMoved, + MouseButtonPressed, + MouseButtonReleased, + MouseWheel, } mType; - std::variant mValue; + std::variant mValue; }; virtual void inputEvent(const InputEvent& event) = 0; diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 55b50b91ae..91ccd4e0a7 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -10,6 +10,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -118,6 +119,8 @@ namespace MWInput mBindingsManager->setPlayerControlsEnabled(!guiMode); mBindingsManager->mouseReleased(arg, id); + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::MouseButtonReleased, arg.button }); } } @@ -125,7 +128,11 @@ namespace MWInput { MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); if (mBindingsManager->isDetectingBindingState() || !input->controlsDisabled()) + { mBindingsManager->mouseWheelMoved(arg); + MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::MouseWheel, + MWBase::LuaManager::InputEvent::WheelChange{ arg.x, arg.y } }); + } input->setJoystickLastUsed(false); } @@ -161,7 +168,11 @@ namespace MWInput const MWGui::SettingsWindow* settingsWindow = MWBase::Environment::get().getWindowManager()->getSettingsWindow(); if ((!settingsWindow || !settingsWindow->isVisible()) && !input->controlsDisabled()) + { mBindingsManager->mousePressed(arg, id); + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::MouseButtonPressed, arg.button }); + } } void MouseManager::updateCursorMode() diff --git a/apps/openmw/mwlua/inputprocessor.hpp b/apps/openmw/mwlua/inputprocessor.hpp index e005183098..dcd19ae8cd 100644 --- a/apps/openmw/mwlua/inputprocessor.hpp +++ b/apps/openmw/mwlua/inputprocessor.hpp @@ -18,7 +18,7 @@ namespace MWLua { mScriptsContainer->registerEngineHandlers({ &mKeyPressHandlers, &mKeyReleaseHandlers, &mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mTouchpadPressed, - &mTouchpadReleased, &mTouchpadMoved }); + &mTouchpadReleased, &mTouchpadMoved, &mMouseButtonPress, &mMouseButtonRelease, &mMouseWheel }); } void processInputEvent(const MWBase::LuaManager::InputEvent& event) @@ -53,6 +53,16 @@ namespace MWLua case InputEvent::TouchMoved: mScriptsContainer->callEngineHandlers(mTouchpadMoved, std::get(event.mValue)); break; + case InputEvent::MouseButtonPressed: + mScriptsContainer->callEngineHandlers(mMouseButtonPress, std::get(event.mValue)); + break; + case InputEvent::MouseButtonReleased: + mScriptsContainer->callEngineHandlers(mMouseButtonRelease, std::get(event.mValue)); + break; + case InputEvent::MouseWheel: + auto wheelEvent = std::get(event.mValue); + mScriptsContainer->callEngineHandlers(mMouseWheel, wheelEvent.y, wheelEvent.x); + break; } } @@ -66,6 +76,9 @@ namespace MWLua typename Container::EngineHandlerList mTouchpadPressed{ "onTouchPress" }; typename Container::EngineHandlerList mTouchpadReleased{ "onTouchRelease" }; typename Container::EngineHandlerList mTouchpadMoved{ "onTouchMove" }; + typename Container::EngineHandlerList mMouseButtonPress{ "onMouseButtonPress" }; + typename Container::EngineHandlerList mMouseButtonRelease{ "onMouseButtonRelease" }; + typename Container::EngineHandlerList mMouseWheel{ "onMouseWheel" }; }; } diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 754a63b314..2b5e99e6ae 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -124,6 +124,15 @@ Engine handler is a function defined by a script, that can be called by the engi * - onTouchMove(touchEvent) - | A finger moved on a touch device. | `Touch event `_. + * - onMouseButtonPress(button) + - | A mouse button was pressed + | Button id + * - onMouseButtonRelease(button) + - | A mouse button was released + | Button id + * - onMouseWheel(vertical, horizontal) + - | Mouse wheel was scrolled + | vertical and horizontal mouse wheel change * - | onConsoleCommand( | mode, command, selectedObject) - | User entered `command` in in-game console. Called if either From 887d09e051cee888d64e774ff56c0d3a9baedb28 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 11 Feb 2024 04:02:01 +0300 Subject: [PATCH 044/451] Fix ESM4 marker model hiding hack --- apps/openmw/mwclass/esm4base.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/esm4base.hpp b/apps/openmw/mwclass/esm4base.hpp index f5fd346637..f13d6007cd 100644 --- a/apps/openmw/mwclass/esm4base.hpp +++ b/apps/openmw/mwclass/esm4base.hpp @@ -103,7 +103,7 @@ namespace MWClass // Hide meshes meshes/marker/* and *LOD.nif in ESM4 cells. It is a temporarty hack. // Needed because otherwise LOD meshes are rendered on top of normal meshes. // TODO: Figure out a better way find markers and LOD meshes; show LOD only outside of active grid. - if (model.empty() || Misc::StringUtils::ciStartsWith(model, "meshes\\marker") + if (model.empty() || Misc::StringUtils::ciStartsWith(model, "marker") || Misc::StringUtils::ciEndsWith(model, "lod.nif")) return {}; From 1e079353663d34d4eb8ab4f9640a67f7e5ba95d6 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 11 Feb 2024 03:12:52 +0100 Subject: [PATCH 045/451] Make crashCatcherInstall no-op for Android The crashcatcher.cpp is not linked on Android because it's not supported but the function need to have some definition. Make it empty to avoid link failures. --- components/crashcatcher/crashcatcher.cpp | 3 --- components/crashcatcher/crashcatcher.hpp | 5 +++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 9a662c4a92..009029ef19 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -583,8 +583,6 @@ static bool isDebuggerPresent() void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& crashLogPath) { -#if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) \ - || defined(__posix)) if (argc == 2 && strcmp(argv[1], crash_switch) == 0) handleCrash(Files::pathToUnicodeString(crashLogPath).c_str()); @@ -595,5 +593,4 @@ void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& cra Log(Debug::Info) << "Crash handler installed"; else Log(Debug::Warning) << "Installing crash handler failed"; -#endif } diff --git a/components/crashcatcher/crashcatcher.hpp b/components/crashcatcher/crashcatcher.hpp index 9dd1000385..16b416cf98 100644 --- a/components/crashcatcher/crashcatcher.hpp +++ b/components/crashcatcher/crashcatcher.hpp @@ -3,6 +3,11 @@ #include +#if (defined(__APPLE__) || (defined(__linux) && !defined(ANDROID)) || (defined(__unix) && !defined(ANDROID)) \ + || defined(__posix)) void crashCatcherInstall(int argc, char** argv, const std::filesystem::path& crashLogPath); +#else +inline void crashCatcherInstall(int /*argc*/, char** /*argv*/, const std::filesystem::path& /*crashLogPath*/) {} +#endif #endif From f4fed4ca5f9729c94e98118afb464918e952e490 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 11 Feb 2024 16:27:54 +0100 Subject: [PATCH 046/451] Simplify inputBinding renderer, allow binding controller buttons --- .../lua-scripting/setting_renderers.rst | 7 +- .../data/scripts/omw/input/actionbindings.lua | 130 ++++++++++++------ files/data/scripts/omw/input/settings.lua | 92 ++++++++++--- 3 files changed, 166 insertions(+), 63 deletions(-) diff --git a/docs/source/reference/lua-scripting/setting_renderers.rst b/docs/source/reference/lua-scripting/setting_renderers.rst index 7f40eb08bd..f817b789bf 100644 --- a/docs/source/reference/lua-scripting/setting_renderers.rst +++ b/docs/source/reference/lua-scripting/setting_renderers.rst @@ -143,10 +143,9 @@ Table with the following fields: * - name - type (default) - description - * - type - - 'keyboardPress', 'keyboardHold' - - The type of input that's allowed to be bound * - key - #string - Key of the action or trigger to which the input is bound - + * - type + - 'action', 'trigger' + - Type of the key diff --git a/files/data/scripts/omw/input/actionbindings.lua b/files/data/scripts/omw/input/actionbindings.lua index 35a467fb52..bc871a3934 100644 --- a/files/data/scripts/omw/input/actionbindings.lua +++ b/files/data/scripts/omw/input/actionbindings.lua @@ -1,11 +1,7 @@ -local core = require('openmw.core') local input = require('openmw.input') local util = require('openmw.util') local async = require('openmw.async') local storage = require('openmw.storage') -local ui = require('openmw.ui') - -local I = require('openmw.interfaces') local actionPressHandlers = {} local function onActionPress(id, handler) @@ -89,48 +85,87 @@ end local bindingSection = storage.playerSection('OMWInputBindings') -local keyboardPresses = {} -local keybordHolds = {} -local boundActions = {} +local devices = { + keyboard = true, + mouse = true, + controller = true +} + +local function invalidBinding(binding) + if not binding.key then + return 'has no key' + elseif binding.type ~= 'action' and binding.type ~= 'trigger' then + return string.format('has invalid type', binding.type) + elseif binding.type == 'action' and not input.actions[binding.key] then + return string.format("action %s doesn't exist", binding.key) + elseif binding.type == 'trigger' and not input.triggers[binding.key] then + return string.format("trigger %s doesn't exist", binding.key) + elseif not binding.device or not devices[binding.device] then + return string.format("invalid device %s", binding.device) + elseif not binding.button then + return 'has no button' + end +end -local function bindAction(action) - if boundActions[action] then return end - boundActions[action] = true - input.bindAction(action, async:callback(function() - if keybordHolds[action] then - for _, binding in pairs(keybordHolds[action]) do - if input.isKeyPressed(binding.code) then return true end +local boundActions = {} +local actionBindings = {} + +local function bindAction(binding, id) + local action = binding.key + actionBindings[action] = actionBindings[action] or {} + actionBindings[action][id] = binding + if not boundActions[action] then + boundActions[binding.key] = true + input.bindAction(action, async:callback(function() + for _, binding in pairs(actionBindings[action] or {}) do + if binding.device == 'keyboard' then + if input.isKeyPressed(binding.button) then + return true + end + elseif binding.device == 'mouse' then + if input.isMouseButtonPressed(binding.button) then + return true + end + elseif binding.device == 'controller' then + if input.isControllerButtonPressed(binding.button) then + return true + end + end end - end - return false - end), {}) + return false + end), {}) + end +end + +local triggerBindings = {} +for device in pairs(devices) do triggerBindings[device] = {} end + +local function bindTrigger(binding, id) + local deviceBindings = triggerBindings[binding.device] + deviceBindings[binding.button] = deviceBindings[binding.button] or {} + deviceBindings[binding.button][id] = binding end local function registerBinding(binding, id) - if not input.actions[binding.key] and not input.triggers[binding.key] then - print(string.format('Skipping binding for unknown action or trigger: "%s"', binding.key)) - return - end - if binding.type == 'keyboardPress' then - local bindings = keyboardPresses[binding.code] or {} - bindings[id] = binding - keyboardPresses[binding.code] = bindings - elseif binding.type == 'keyboardHold' then - local bindings = keybordHolds[binding.key] or {} - bindings[id] = binding - keybordHolds[binding.key] = bindings - bindAction(binding.key) - else - error('Unknown binding type "' .. binding.type .. '"') + local invalid = invalidBinding(binding) + if invalid then + print(string.format('Skipping invalid binding %s: %s', id, invalid)) + elseif binding.type == 'action' then + bindAction(binding, id) + elseif binding.type == 'trigger' then + bindTrigger(binding, id) end end function clearBinding(id) - for _, boundTriggers in pairs(keyboardPresses) do - boundTriggers[id] = nil + for _, deviceBindings in pairs(triggerBindings) do + for _, buttonBindings in pairs(deviceBindings) do + buttonBindings[id] = nil + end end - for _, boundKeys in pairs(keybordHolds) do - boundKeys[id] = nil + + for _, bindings in pairs(actionBindings) do + bindings[id] = nil end end @@ -170,11 +205,24 @@ return { end end, onKeyPress = function(e) - local bindings = keyboardPresses[e.code] - if bindings then - for _, binding in pairs(bindings) do - input.activateTrigger(binding.key) - end + local buttonTriggers = triggerBindings.keyboard[e.code] + if not buttonTriggers then return end + for _, binding in pairs(buttonTriggers) do + input.activateTrigger(binding.key) + end + end, + onMouseButtonPress = function(button) + local buttonTriggers = triggerBindings.mouse[button] + if not buttonTriggers then return end + for _, binding in pairs(buttonTriggers) do + input.activateTrigger(binding.key) + end + end, + onControllerButtonPress = function(id) + local buttonTriggers = triggerBindings.controller[id] + if not buttonTriggers then return end + for _, binding in pairs(buttonTriggers) do + input.activateTrigger(binding.key) end end, } diff --git a/files/data/scripts/omw/input/settings.lua b/files/data/scripts/omw/input/settings.lua index 6c1b857131..3c1ba4d6b9 100644 --- a/files/data/scripts/omw/input/settings.lua +++ b/files/data/scripts/omw/input/settings.lua @@ -44,14 +44,63 @@ local bindingSection = storage.playerSection('OMWInputBindings') local recording = nil +local mouseButtonNames = { + [1] = 'Left', + [2] = 'Middle', + [3] = 'Right', + [4] = '4', + [5] = '5', +} + +-- TODO: support different controllers, use icons to render controller buttons +local controllerButtonNames = { + [-1] = 'Invalid', + [input.CONTROLLER_BUTTON.A] = "A", + [input.CONTROLLER_BUTTON.B] = "B", + [input.CONTROLLER_BUTTON.X] = "X", + [input.CONTROLLER_BUTTON.Y] = "Y", + [input.CONTROLLER_BUTTON.Back] = "Back", + [input.CONTROLLER_BUTTON.Guide] = "Guide", + [input.CONTROLLER_BUTTON.Start] = "Start", + [input.CONTROLLER_BUTTON.LeftStick] = "Left Stick", + [input.CONTROLLER_BUTTON.RightStick] = "Right Stick", + [input.CONTROLLER_BUTTON.LeftShoulder] = "LB", + [input.CONTROLLER_BUTTON.RightShoulder] = "RB", + [input.CONTROLLER_BUTTON.DPadUp] = "D-pad Up", + [input.CONTROLLER_BUTTON.DPadDown] = "D-pad Down", + [input.CONTROLLER_BUTTON.DPadLeft] = "D-pad Left", + [input.CONTROLLER_BUTTON.DPadRight] = "D-pad Right", +} + +local function bindingLabel(recording, binding) + if recording then + return interfaceL10n('N/A') + elseif not binding or not binding.button then + return interfaceL10n('None') + elseif binding.device == 'keyboard' then + return input.getKeyName(binding.button) + elseif binding.device == 'mouse' then + return string.format('Mouse %s', mouseButtonNames[binding.button] or 'Unknown') + elseif binding.device == 'controller' then + return string.format('Controller %s', controllerButtonNames[binding.button] or 'Unknown') + else + return 'Unknown' + end +end + +local inputTypes = { + action = input.actions, + trigger = input.triggers, +} I.Settings.registerRenderer('inputBinding', function(id, set, arg) if type(id) ~= 'string' then error('inputBinding: must have a string default value') end if not arg then error('inputBinding: argument with "key" and "type" is required') end if not arg.type then error('inputBinding: type argument is required') end + if not inputTypes[arg.type] then error('inputBinding: type must be "action" or "trigger"') end if not arg.key then error('inputBinding: key argument is required') end - local info = input.actions[arg.key] or input.triggers[arg.key] - if not info then return {} end + local info = inputTypes[arg.type][arg.key] + if not info then print(string.format('inputBinding: %s %s not found', arg.type, arg.key)) return end local l10n = core.l10n(info.key) @@ -70,9 +119,7 @@ I.Settings.registerRenderer('inputBinding', function(id, set, arg) } local binding = bindingSection:get(id) - local label = interfaceL10n('None') - if binding then label = input.getKeyName(binding.code) end - if recording and recording.id == id then label = interfaceL10n('N/A') end + local label = bindingLabel(recording and recording.id == id, binding) local recorder = { template = I.MWUI.templates.textNormal, @@ -115,22 +162,31 @@ I.Settings.registerRenderer('inputBinding', function(id, set, arg) return column end) +local function bindButton(device, button) + if recording == nil then return end + local binding = { + device = device, + button = button, + type = recording.arg.type, + key = recording.arg.key, + } + bindingSection:set(recording.id, binding) + local refresh = recording.refresh + recording = nil + refresh() +end + return { engineHandlers = { onKeyPress = function(key) - if recording == nil then return end - local binding = { - code = key.code, - type = recording.arg.type, - key = recording.arg.key, - } - if key.code == input.KEY.Escape then -- TODO: prevent settings modal from closing - binding.code = nil - end - bindingSection:set(recording.id, binding) - local refresh = recording.refresh - recording = nil - refresh() + bindButton(key.code ~= input.KEY.Escape and 'keyboard' or nil, key.code) + end, + -- TODO: currently never triggers, because mouse events are disabled while inside settings + onMouseButtonPress = function(button) + bindButton('mouse', button) + end, + onControllerButtonPress = function(id) + bindButton('controller', id) end, } } From 75d0b6e3551c4f6ebd89cd1bae08dbbdfac9dc80 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 11 Feb 2024 22:06:58 +0100 Subject: [PATCH 047/451] Use decompose to handle AI packages and data --- apps/esmtool/record.cpp | 6 -- apps/openmw_test_suite/esm3/testsaveload.cpp | 44 +++++++++++++- components/esm3/aipackage.cpp | 61 ++++++++++++++------ components/esm3/aipackage.hpp | 16 ++--- components/esm3/loadcrea.cpp | 5 +- components/esm3/loadnpc.cpp | 5 +- 6 files changed, 100 insertions(+), 37 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index e3b81daf41..245012ce13 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -722,9 +722,6 @@ namespace EsmTool std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl; std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl; - std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; - std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; - std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; for (const ESM::AIPackage& package : mData.mAiPackage.mList) @@ -1115,9 +1112,6 @@ namespace EsmTool std::cout << " AI Fight:" << (int)mData.mAiData.mFight << std::endl; std::cout << " AI Flee:" << (int)mData.mAiData.mFlee << std::endl; std::cout << " AI Alarm:" << (int)mData.mAiData.mAlarm << std::endl; - std::cout << " AI U1:" << (int)mData.mAiData.mU1 << std::endl; - std::cout << " AI U2:" << (int)mData.mAiData.mU2 << std::endl; - std::cout << " AI U3:" << (int)mData.mAiData.mU3 << std::endl; std::cout << " AI Services:" << Misc::StringUtils::format("0x%08X", mData.mAiData.mServices) << std::endl; for (const ESM::AIPackage& package : mData.mAiPackage.mList) diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index f8ef23e887..8010e1d7ef 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -89,8 +90,18 @@ namespace ESM constexpr std::uint32_t fakeRecordId = fourCC("FAKE"); + template > + struct HasSave : std::false_type + { + }; + template - void save(const T& record, ESMWriter& writer) + struct HasSave().save(std::declval()))>> : std::true_type + { + }; + + template + auto save(const T& record, ESMWriter& writer) -> std::enable_if_t>::value> { record.save(writer); } @@ -100,6 +111,12 @@ namespace ESM record.save(writer, true); } + template + auto save(const T& record, ESMWriter& writer) -> std::enable_if_t>::value> + { + writer.writeComposite(record); + } + template std::unique_ptr makeEsmStream(const T& record, FormatVersion formatVersion) { @@ -154,6 +171,12 @@ namespace ESM record.load(reader, deleted, true); } + template + auto load(ESMReader& reader, T& record) -> std::enable_if_t>::value> + { + reader.getComposite(record); + } + template void saveAndLoadRecord(const T& record, FormatVersion formatVersion, T& result) { @@ -490,6 +513,25 @@ namespace ESM EXPECT_EQ(result.mRepeat, record.mRepeat); } + TEST_P(Esm3SaveLoadRecordTest, aiDataShouldNotChange) + { + AIData record; + record.mHello = 1; + record.mFight = 2; + record.mFlee = 3; + record.mAlarm = 4; + record.mServices = 5; + + AIData result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mHello, record.mHello); + EXPECT_EQ(result.mFight, record.mFight); + EXPECT_EQ(result.mFlee, record.mFlee); + EXPECT_EQ(result.mAlarm, record.mAlarm); + EXPECT_EQ(result.mServices, record.mServices); + } + INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats())); } } diff --git a/components/esm3/aipackage.cpp b/components/esm3/aipackage.cpp index 4d4c03c349..2cadb9fb22 100644 --- a/components/esm3/aipackage.cpp +++ b/components/esm3/aipackage.cpp @@ -5,9 +5,35 @@ namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mDistance, v.mDuration, v.mTimeOfDay, v.mIdle, v.mShouldRepeat); + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding[3] = { 0, 0, 0 }; + f(v.mX, v.mY, v.mZ, v.mShouldRepeat, padding); + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding = 0; + f(v.mX, v.mY, v.mZ, v.mDuration, v.mId.mData, v.mShouldRepeat, padding); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mName.mData, v.mShouldRepeat); + } + void AIData::blank() { - mHello = mFight = mFlee = mAlarm = mU1 = mU2 = mU3 = 0; + mHello = mFight = mFlee = mAlarm = 0; mServices = 0; } @@ -28,58 +54,57 @@ namespace ESM else if (esm.retSubName() == AI_Wander) { pack.mType = AI_Wander; - esm.getHExact(&pack.mWander, 14); + esm.getSubHeader(); + esm.getComposite(pack.mWander); mList.push_back(pack); } else if (esm.retSubName() == AI_Travel) { pack.mType = AI_Travel; - esm.getHExact(&pack.mTravel, 16); + esm.getSubHeader(); + esm.getComposite(pack.mTravel); mList.push_back(pack); } else if (esm.retSubName() == AI_Escort || esm.retSubName() == AI_Follow) { pack.mType = (esm.retSubName() == AI_Escort) ? AI_Escort : AI_Follow; - esm.getHExact(&pack.mTarget, 48); + esm.getSubHeader(); + esm.getComposite(pack.mTarget); mList.push_back(pack); } else if (esm.retSubName() == AI_Activate) { pack.mType = AI_Activate; - esm.getHExact(&pack.mActivate, 33); + esm.getSubHeader(); + esm.getComposite(pack.mActivate); mList.push_back(pack); } - else - { // not AI package related data, so leave - return; - } } void AIPackageList::save(ESMWriter& esm) const { - typedef std::vector::const_iterator PackageIter; - for (PackageIter it = mList.begin(); it != mList.end(); ++it) + for (const AIPackage& package : mList) { - switch (it->mType) + switch (package.mType) { case AI_Wander: - esm.writeHNT("AI_W", it->mWander, sizeof(it->mWander)); + esm.writeNamedComposite("AI_W", package.mWander); break; case AI_Travel: - esm.writeHNT("AI_T", it->mTravel, sizeof(it->mTravel)); + esm.writeNamedComposite("AI_T", package.mTravel); break; case AI_Activate: - esm.writeHNT("AI_A", it->mActivate, sizeof(it->mActivate)); + esm.writeNamedComposite("AI_A", package.mActivate); break; case AI_Escort: case AI_Follow: { - const NAME name = (it->mType == AI_Escort) ? NAME("AI_E") : NAME("AI_F"); - esm.writeHNT(name, it->mTarget, sizeof(it->mTarget)); - esm.writeHNOCString("CNDT", it->mCellName); + const NAME name = (package.mType == AI_Escort) ? NAME("AI_E") : NAME("AI_F"); + esm.writeNamedComposite(name, package.mTarget); + esm.writeHNOCString("CNDT", package.mCellName); break; } diff --git a/components/esm3/aipackage.hpp b/components/esm3/aipackage.hpp index 7346a4af36..10e7be8f00 100644 --- a/components/esm3/aipackage.hpp +++ b/components/esm3/aipackage.hpp @@ -5,20 +5,17 @@ #include #include "components/esm/esmcommon.hpp" +#include "components/misc/concepts.hpp" namespace ESM { class ESMReader; class ESMWriter; -#pragma pack(push) -#pragma pack(1) - struct AIData { uint16_t mHello; // This is the base value for greeting distance [0, 65535] unsigned char mFight, mFlee, mAlarm; // These are probabilities [0, 100] - char mU1, mU2, mU3; // Unknown values int32_t mServices; // See the Services enum void blank(); @@ -38,7 +35,6 @@ namespace ESM { float mX, mY, mZ; unsigned char mShouldRepeat; - unsigned char mPadding[3]; }; struct AITarget @@ -47,7 +43,6 @@ namespace ESM int16_t mDuration; NAME32 mId; unsigned char mShouldRepeat; - unsigned char mPadding; }; struct AIActivate @@ -56,8 +51,6 @@ namespace ESM unsigned char mShouldRepeat; }; -#pragma pack(pop) - enum AiPackageType : std::uint32_t { AI_Wander = 0x575f4941, @@ -98,6 +91,13 @@ namespace ESM void save(ESMWriter& esm) const; }; + + template T> + void decompose(T&& v, const auto& f) + { + char padding[3] = { 0, 0, 0 }; + f(v.mHello, v.mFight, v.mFlee, v.mAlarm, padding, v.mServices); + } } #endif diff --git a/components/esm3/loadcrea.cpp b/components/esm3/loadcrea.cpp index 0c0bad2e7c..1db79e8e76 100644 --- a/components/esm3/loadcrea.cpp +++ b/components/esm3/loadcrea.cpp @@ -69,7 +69,8 @@ namespace ESM mSpells.add(esm); break; case fourCC("AIDT"): - esm.getHExact(&mAiData, sizeof(mAiData)); + esm.getSubHeader(); + esm.getComposite(mAiData); break; case fourCC("DODT"): case fourCC("DNAM"): @@ -130,7 +131,7 @@ namespace ESM mInventory.save(esm); mSpells.save(esm); - esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); + esm.writeNamedComposite("AIDT", mAiData); mTransport.save(esm); mAiPackage.save(esm); } diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 4a30649372..92b16638c2 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -102,7 +102,8 @@ namespace ESM mInventory.add(esm); break; case fourCC("AIDT"): - esm.getHExact(&mAiData, sizeof(mAiData)); + esm.getSubHeader(); + esm.getComposite(mAiData); break; case fourCC("DODT"): case fourCC("DNAM"): @@ -186,7 +187,7 @@ namespace ESM mInventory.save(esm); mSpells.save(esm); - esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); + esm.writeNamedComposite("AIDT", mAiData); mTransport.save(esm); From 63a1bbb88d7829152f1b3ef17c3cc7c7b007f542 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 11 Feb 2024 23:49:26 +0100 Subject: [PATCH 048/451] Enable Lua mouse engine handlers while in UI --- apps/openmw/mwinput/mousemanager.cpp | 13 +++++++------ files/data/scripts/omw/input/settings.lua | 1 - 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 91ccd4e0a7..ffbe40a2db 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -119,9 +119,10 @@ namespace MWInput mBindingsManager->setPlayerControlsEnabled(!guiMode); mBindingsManager->mouseReleased(arg, id); - MWBase::Environment::get().getLuaManager()->inputEvent( - { MWBase::LuaManager::InputEvent::MouseButtonReleased, arg.button }); } + + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::MouseButtonReleased, arg.button }); } void MouseManager::mouseWheelMoved(const SDL_MouseWheelEvent& arg) @@ -130,11 +131,11 @@ namespace MWInput if (mBindingsManager->isDetectingBindingState() || !input->controlsDisabled()) { mBindingsManager->mouseWheelMoved(arg); - MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::MouseWheel, - MWBase::LuaManager::InputEvent::WheelChange{ arg.x, arg.y } }); } input->setJoystickLastUsed(false); + MWBase::Environment::get().getLuaManager()->inputEvent({ MWBase::LuaManager::InputEvent::MouseWheel, + MWBase::LuaManager::InputEvent::WheelChange{ arg.x, arg.y } }); } void MouseManager::mousePressed(const SDL_MouseButtonEvent& arg, Uint8 id) @@ -170,9 +171,9 @@ namespace MWInput if ((!settingsWindow || !settingsWindow->isVisible()) && !input->controlsDisabled()) { mBindingsManager->mousePressed(arg, id); - MWBase::Environment::get().getLuaManager()->inputEvent( - { MWBase::LuaManager::InputEvent::MouseButtonPressed, arg.button }); } + MWBase::Environment::get().getLuaManager()->inputEvent( + { MWBase::LuaManager::InputEvent::MouseButtonPressed, arg.button }); } void MouseManager::updateCursorMode() diff --git a/files/data/scripts/omw/input/settings.lua b/files/data/scripts/omw/input/settings.lua index 3c1ba4d6b9..5243a86844 100644 --- a/files/data/scripts/omw/input/settings.lua +++ b/files/data/scripts/omw/input/settings.lua @@ -181,7 +181,6 @@ return { onKeyPress = function(key) bindButton(key.code ~= input.KEY.Escape and 'keyboard' or nil, key.code) end, - -- TODO: currently never triggers, because mouse events are disabled while inside settings onMouseButtonPress = function(button) bindButton('mouse', button) end, From 3149761c853395c86aa057017fdbde5d059b25c9 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 11 Feb 2024 23:49:18 +0000 Subject: [PATCH 049/451] Fix grammar for A2C checkbox An alternative would be *Anti-alias alpha testing*. The original was wrong because anti-alias is a verb acting on alpha testing, but it treated the whole thing as a noun phrase. --- apps/launcher/ui/settingspage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 2d06c1802e..a59891eb54 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -524,7 +524,7 @@ <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - Use anti-alias alpha testing + Use anti-aliased alpha testing From 5913ca67d572f8df0ae3f4d026685f697798de97 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 11 Feb 2024 23:53:58 +0000 Subject: [PATCH 050/451] .ts files, too --- files/lang/launcher_de.ts | 2 +- files/lang/launcher_fr.ts | 2 +- files/lang/launcher_ru.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 1c23f90425..3b911e2288 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -852,7 +852,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Use anti-alias alpha testing + Use anti-aliased alpha testing diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index f604044368..757121d1d3 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -852,7 +852,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Use anti-alias alpha testing + Use anti-aliased alpha testing diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 844e36898c..dad899aff6 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -859,7 +859,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Позволяет MSAA работать с моделями с альфа-тестированием, что позволяет улучшить отображение граней и избежать их пикселизации. Может снизить производительность.</p></body></html> - Use anti-alias alpha testing + Use anti-aliased alpha testing Сглаживание альфа-тестирования From 8c591a0b44c5d701c2c60d3325f999aa150bef93 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 12 Feb 2024 01:16:49 +0000 Subject: [PATCH 051/451] Groundcover should ignore non-geometry Drawables Fix https://gitlab.com/OpenMW/openmw/-/issues/7633 Untested - the issue didn't link to a mod using the mesh and I couldn't be bothered setting one up manually. --- CHANGELOG.md | 1 + apps/openmw/mwrender/groundcover.cpp | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a96eb36c9f..b4177f5398 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,6 +111,7 @@ Bug #7619: Long map notes may get cut off Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing + Bug #7633: Groundcover should ignore non-geometry Drawables 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 diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 92be726f09..8656af9d2f 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -196,6 +196,18 @@ namespace MWRender { } + void apply(osg::Group& group) override + { + for (unsigned int i = 0; i < group.getNumChildren();) + { + if (group.getChild(i)->asDrawable() && !group.getChild(i)->asGeometry()) + group.removeChild(i); + else + ++i; + } + traverse(group); + } + void apply(osg::Geometry& geom) override { for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) From 567d36240eec88d8b1ae200e0911a94e4da86fc1 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 12 Feb 2024 15:02:21 +0000 Subject: [PATCH 052/451] Clarify shaders documentation We know people get confused by it. Hopefully this should help. --- .../reference/modding/settings/shaders.rst | 42 ++++++++++--------- files/settings-default.cfg | 13 +++--- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 9ee1cbfaa5..8bd152857f 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -8,11 +8,15 @@ force shaders :Range: True/False :Default: False -Force rendering with shaders. By default, only bump-mapped objects will use shaders. -Enabling this option may cause slightly different visuals if the "clamp lighting" option is set to false. +Force rendering with shaders, even for objects that don't strictly need them. +By default, only objects with certain effects, such as bump or normal maps will use shaders. +Many visual enhancements, such as :ref:`enable shadows` and :ref:`reverse z` require shaders to be used for all objects, and so behave as if this setting is true. +Typically, one or more of these enhancements will be enabled, and shaders will be needed for everything anyway, meaning toggling this setting will have no effect. + +Some settings, such as :ref:`clamp lighting` only apply to objects using shaders, so enabling this option may cause slightly different visuals when used at the same time. Otherwise, there should not be a visual difference. -Please note enabling shaders has a significant performance impact on most systems. +Please note enabling shaders may have a significant performance impact on some systems, and a mild impact on many others. force per pixel lighting ------------------------ @@ -21,10 +25,10 @@ force per pixel lighting :Range: True/False :Default: False -Force the use of per pixel lighting. By default, only bump mapped objects use per-pixel lighting. -Has no effect if the 'force shaders' option is false. +Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. +Only affects objects drawn with shaders (see :ref:`force shaders` option). Enabling per-pixel lighting results in visual differences to the original MW engine as certain lights in Morrowind rely on vertex lighting to look as intended. -Note that groundcover shaders ignore this setting. +Note that groundcover shaders and particle effects ignore this setting. clamp lighting -------------- @@ -34,7 +38,7 @@ clamp lighting :Default: True Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). -Only affects objects that render with shaders (see 'force shaders' option). +Only affects objects drawn with shaders (see :ref:`force shaders` option). Always affects terrain. Leaving this option at its default makes the lighting compatible with Morrowind's fixed-function method, @@ -49,9 +53,9 @@ auto use object normal maps :Default: False If this option is enabled, normal maps are automatically recognized and used if they are named appropriately -(see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). +(see :ref:`normal map pattern`, e.g. for a base texture ``foo.dds``, the normal map texture would have to be named ``foo_n.dds``). If this option is disabled, -normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects. +normal maps are only used if they are explicitly listed within the mesh file (``.nif`` or ``.osg`` file). Affects objects. auto use object specular maps ----------------------------- @@ -61,10 +65,10 @@ auto use object specular maps :Default: False If this option is enabled, specular maps are automatically recognized and used if they are named appropriately -(see 'specular map pattern', e.g. for a base texture foo.dds, -the specular map texture would have to be named foo_spec.dds). +(see :ref:`specular map pattern`, e.g. for a base texture ``foo.dds``, +the specular map texture would have to be named ``foo_spec.dds``). If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file -(.osg file, not supported in .nif files). Affects objects. +(``.osg`` file, not supported in ``.nif`` files). Affects objects. auto use terrain normal maps ---------------------------- @@ -73,7 +77,7 @@ auto use terrain normal maps :Range: True/False :Default: False -See 'auto use object normal maps'. Affects terrain. +See :ref:`auto use object normal maps`. Affects terrain. auto use terrain specular maps ------------------------------ @@ -82,7 +86,7 @@ auto use terrain specular maps :Range: True/False :Default: False -If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. +If a file with pattern :ref:`terrain specular map pattern` exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel. normal map pattern @@ -93,7 +97,7 @@ normal map pattern :Default: _n The filename pattern to probe for when detecting normal maps -(see 'auto use object normal maps', 'auto use terrain normal maps') +(see :ref:`auto use object normal maps`, :ref:`auto use terrain normal maps`) normal height map pattern ------------------------- @@ -113,7 +117,7 @@ specular map pattern :Range: :Default: _spec -The filename pattern to probe for when detecting object specular maps (see 'auto use object specular maps') +The filename pattern to probe for when detecting object specular maps (see :ref:`auto use object specular maps`) terrain specular map pattern ---------------------------- @@ -122,7 +126,7 @@ terrain specular map pattern :Range: :Default: _diffusespec -The filename pattern to probe for when detecting terrain specular maps (see 'auto use terrain specular maps') +The filename pattern to probe for when detecting terrain specular maps (see :ref:`auto use terrain specular maps`) apply lighting to environment maps ---------------------------------- @@ -166,7 +170,7 @@ normal maps are provided. This is due to some groundcover mods using the Z-Up normals technique to avoid some common issues with shading. As a consequence, per pixel lighting would give undesirable results. -Note that the rendering will act as if you have 'force shaders' option enabled +Note that the rendering will act as if you have :ref:`force shaders` option enabled when not set to 'legacy'. This means that shaders will be used to render all objects and the terrain. @@ -283,7 +287,7 @@ between them. Note, this relies on overriding specific properties of particle systems that potentially differ from the source content, this setting may change the look of some particle systems. -Note that the rendering will act as if you have 'force shaders' option enabled. +Note that the rendering will act as if you have :ref:`force shaders` option enabled. This means that shaders will be used to render all objects and the terrain. weather particle occlusion diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 4a90a46cc5..5f68f055d4 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -405,15 +405,18 @@ console history buffer size = 4096 [Shaders] -# Force rendering with shaders. By default, only bump-mapped objects will use shaders. -# Enabling this option may cause slightly different visuals if the "clamp lighting" option -# is set to false. Otherwise, there should not be a visual difference. +# Force rendering with shaders, even for objects that don't strictly need them. +# By default, only objects with certain effects, such as bump or normal maps will use shaders. +# Many visual enhancements, such as "enable shadows" and "reverse z" require shaders to be used for all objects, and so behave as if this setting is true. +# Some settings, such as "clamp lighting" only apply to objects using shaders, so enabling this option may cause slightly different visuals when used at the same time. +# Otherwise, there should not be a visual difference. force shaders = false -# Force the use of per pixel lighting. By default, only bump mapped objects use per-pixel lighting. -# Has no effect if the 'force shaders' option is false. +# Force the use of per pixel lighting. By default, only bump and normal mapped objects use per-pixel lighting. +# Only affects objects drawn with shaders (see "force shaders" option). # Enabling per-pixel lighting can result in visual differences to the original MW engine as # certain lights in Morrowind rely on vertex lighting to look as intended. +# Note that groundcover shaders and particle effects ignore this setting. force per pixel lighting = false # Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). From 56b31ceaf55b9086e6ab8c07ca1143adbf65a64c Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Mon, 12 Feb 2024 07:52:47 -0800 Subject: [PATCH 053/451] add ignore list to raycasts --- CMakeLists.txt | 2 +- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwlua/nearbybindings.cpp | 69 +++++++-- apps/openmw/mwmechanics/aiwander.cpp | 2 +- .../closestnotmerayresultcallback.cpp | 2 +- .../closestnotmerayresultcallback.hpp | 8 +- apps/openmw/mwphysics/physicssystem.cpp | 26 ++-- apps/openmw/mwphysics/physicssystem.hpp | 9 +- apps/openmw/mwphysics/raycasting.hpp | 11 +- apps/openmw/mwrender/renderingmanager.cpp | 131 +++++++++++++++--- apps/openmw/mwrender/renderingmanager.hpp | 13 +- apps/openmw/mwworld/scene.cpp | 19 +-- apps/openmw/mwworld/scene.hpp | 2 + apps/openmw/mwworld/worldimp.cpp | 11 +- apps/openmw/mwworld/worldimp.hpp | 2 +- files/lua_api/openmw/nearby.lua | 7 + 16 files changed, 237 insertions(+), 79 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d62cda4f2b..7b1b18e41e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 53) +set(OPENMW_LUA_API_REVISION 54) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 4247ef2e3e..fe8b5cc13a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -304,7 +304,7 @@ namespace MWBase virtual const MWPhysics::RayCastingInterface* getRayCasting() const = 0; virtual bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, - bool ignorePlayer, bool ignoreActors) + bool ignorePlayer, bool ignoreActors, std::span ignoreList = {}) = 0; virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool internal, bool external) = 0; diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 7e1845aeac..7eda965e96 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -16,6 +16,31 @@ #include "luamanagerimp.hpp" #include "objectlists.hpp" +namespace +{ + template + std::vector parseIgnoreList(const sol::table& options) + { + std::vector ignore; + + if (const auto& ignoreObj = options.get>("ignore")) + { + ignore.push_back(ignoreObj->ptr()); + } + else if (const auto& ignoreTable = options.get>("ignore")) + { + ignoreTable->for_each([&](const auto& _, const sol::object& value) { + if (value.is()) + { + ignore.push_back(value.as().ptr()); + } + }); + } + + return ignore; + } +} + namespace sol { template <> @@ -71,24 +96,27 @@ namespace MWLua })); api["castRay"] = [](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) { - MWWorld::Ptr ignore; + std::vector ignore; int collisionType = MWPhysics::CollisionType_Default; float radius = 0; if (options) { - sol::optional ignoreObj = options->get>("ignore"); - if (ignoreObj) - ignore = ignoreObj->ptr(); + ignore = parseIgnoreList(*options); collisionType = options->get>("collisionType").value_or(collisionType); radius = options->get>("radius").value_or(0); } const MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); if (radius <= 0) - return rayCasting->castRay(from, to, ignore, std::vector(), collisionType); + { + return rayCasting->castRay(from, to, ignore, {}, collisionType); + } else { - if (!ignore.isEmpty()) - throw std::logic_error("Currently castRay doesn't support `ignore` when radius > 0"); + for (const auto& ptr : ignore) + { + if (!ptr.isEmpty()) + throw std::logic_error("Currently castRay doesn't support `ignore` when radius > 0"); + } return rayCasting->castSphere(from, to, radius, collisionType); } }; @@ -108,22 +136,37 @@ namespace MWLua // and use this callback from the main thread at the beginning of the next frame processing. rayCasting->asyncCastRay(callback, from, to, ignore, std::vector(), collisionType); };*/ - api["castRenderingRay"] = [manager = context.mLuaManager](const osg::Vec3f& from, const osg::Vec3f& to) { + api["castRenderingRay"] = [manager = context.mLuaManager](const osg::Vec3f& from, const osg::Vec3f& to, + const sol::optional& options) { if (!manager->isProcessingInputEvents()) { throw std::logic_error( "castRenderingRay can be used only in player scripts during processing of input events; " "use asyncCastRenderingRay instead."); } + + std::vector ignore; + if (options.has_value()) + { + ignore = parseIgnoreList(*options); + } + MWPhysics::RayCastingResult res; - MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); + MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false, ignore); return res; }; - api["asyncCastRenderingRay"] = [context]( - const sol::table& callback, const osg::Vec3f& from, const osg::Vec3f& to) { - context.mLuaManager->addAction([context, callback = LuaUtil::Callback::fromLua(callback), from, to] { + api["asyncCastRenderingRay"] = [context](const sol::table& callback, const osg::Vec3f& from, + const osg::Vec3f& to, const sol::optional& options) { + std::vector ignore; + if (options.has_value()) + { + ignore = parseIgnoreList(*options); + } + + context.mLuaManager->addAction([context, ignore, callback = LuaUtil::Callback::fromLua(callback), from, + to] { MWPhysics::RayCastingResult res; - MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false); + MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false, ignore); context.mLuaManager->queueCallback(callback, sol::main_object(context.mLua->sol(), sol::in_place, res)); }); }; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index be2601dc37..38466c0c4c 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -86,7 +86,7 @@ namespace MWMechanics return MWBase::Environment::get() .getWorld() ->getRayCasting() - ->castRay(position, visibleDestination, actor, {}, mask) + ->castRay(position, visibleDestination, { actor }, {}, mask) .mHit; } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 02c94a6f9d..b63cd568a8 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -12,7 +12,7 @@ namespace MWPhysics btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) { const auto* hitObject = rayResult.m_collisionObject; - if (hitObject == mMe) + if (std::find(mIgnoreList.begin(), mIgnoreList.end(), hitObject) != mIgnoreList.end()) return 1.f; if (hitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor && !mTargets.empty()) diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp index f7f567ed4e..660f24424d 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.hpp @@ -14,10 +14,10 @@ namespace MWPhysics class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - explicit ClosestNotMeRayResultCallback(const btCollisionObject* me, std::span targets, - const btVector3& from, const btVector3& to) + explicit ClosestNotMeRayResultCallback(std::span ignore, + std::span targets, const btVector3& from, const btVector3& to) : btCollisionWorld::ClosestRayResultCallback(from, to) - , mMe(me) + , mIgnoreList(ignore) , mTargets(targets) { } @@ -25,7 +25,7 @@ namespace MWPhysics btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override; private: - const btCollisionObject* mMe; + const std::span mIgnoreList; const std::span mTargets; }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 43888ac306..20a9c38b0f 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -192,7 +192,8 @@ namespace MWPhysics } RayCastingResult PhysicsSystem::castRay(const osg::Vec3f& from, const osg::Vec3f& to, - const MWWorld::ConstPtr& ignore, const std::vector& targets, int mask, int group) const + const std::vector& ignore, const std::vector& targets, int mask, + int group) const { if (from == to) { @@ -203,19 +204,22 @@ namespace MWPhysics btVector3 btFrom = Misc::Convert::toBullet(from); btVector3 btTo = Misc::Convert::toBullet(to); - const btCollisionObject* me = nullptr; + std::vector ignoreList; std::vector targetCollisionObjects; - if (!ignore.isEmpty()) + for (const auto& ptr : ignore) { - const Actor* actor = getActor(ignore); - if (actor) - me = actor->getCollisionObject(); - else + if (!ptr.isEmpty()) { - const Object* object = getObject(ignore); - if (object) - me = object->getCollisionObject(); + const Actor* actor = getActor(ptr); + if (actor) + ignoreList.push_back(actor->getCollisionObject()); + else + { + const Object* object = getObject(ptr); + if (object) + ignoreList.push_back(object->getCollisionObject()); + } } } @@ -229,7 +233,7 @@ namespace MWPhysics } } - ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo); + ClosestNotMeRayResultCallback resultCallback(ignoreList, targetCollisionObjects, btFrom, btTo); resultCallback.m_collisionFilterGroup = group; resultCallback.m_collisionFilterMask = mask; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 6734682092..9cc55fecc6 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -209,12 +209,11 @@ namespace MWPhysics const MWWorld::ConstPtr& ptr, int collisionGroup, int collisionMask) const; osg::Vec3f traceDown(const MWWorld::Ptr& ptr, const osg::Vec3f& position, float maxHeight); - /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all - /// other actors. + /// @param ignore Optional, a list of Ptr to ignore in the list of results. targets are actors to filter for, + /// ignoring all other actors. RayCastingResult castRay(const osg::Vec3f& from, const osg::Vec3f& to, - const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), - const std::vector& targets = std::vector(), int mask = CollisionType_Default, - int group = 0xff) const override; + const std::vector& ignore = {}, const std::vector& targets = {}, + int mask = CollisionType_Default, int group = 0xff) const override; using RayCastingInterface::castRay; RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius, diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index 6b1a743d54..78b6ab4678 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -23,16 +23,15 @@ namespace MWPhysics public: virtual ~RayCastingInterface() = default; - /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all - /// other actors. + /// @param ignore Optional, a list of Ptr to ignore in the list of results. targets are actors to filter for, + /// ignoring all other actors. virtual RayCastingResult castRay(const osg::Vec3f& from, const osg::Vec3f& to, - const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), - const std::vector& targets = std::vector(), int mask = CollisionType_Default, - int group = 0xff) const = 0; + const std::vector& ignore = {}, const std::vector& targets = {}, + int mask = CollisionType_Default, int group = 0xff) const = 0; RayCastingResult castRay(const osg::Vec3f& from, const osg::Vec3f& to, int mask) const { - return castRay(from, to, MWWorld::ConstPtr(), std::vector(), mask); + return castRay(from, to, {}, {}, mask); } virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius, diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 15faabb6df..224774d102 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -59,6 +59,7 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/groundcoverstore.hpp" +#include "../mwworld/scene.hpp" #include "../mwgui/postprocessorhud.hpp" @@ -1014,20 +1015,17 @@ namespace MWRender return osg::Vec4f(min_x, min_y, max_x, max_y); } - RenderingManager::RayResult getIntersectionResult(osgUtil::LineSegmentIntersector* intersector) + RenderingManager::RayResult getIntersectionResult(osgUtil::LineSegmentIntersector* intersector, + const osg::ref_ptr& visitor, std::span ignoreList = {}) { RenderingManager::RayResult result; result.mHit = false; result.mRatio = 0; - if (intersector->containsIntersections()) - { - result.mHit = true; - osgUtil::LineSegmentIntersector::Intersection intersection = intersector->getFirstIntersection(); - result.mHitPointWorld = intersection.getWorldIntersectPoint(); - result.mHitNormalWorld = intersection.getWorldIntersectNormal(); - result.mRatio = intersection.ratio; + if (!intersector->containsIntersections()) + return result; + auto test = [&](const osgUtil::LineSegmentIntersector::Intersection& intersection) { PtrHolder* ptrHolder = nullptr; std::vector refnumMarkers; for (osg::NodePath::const_iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); @@ -1039,9 +1037,16 @@ namespace MWRender for (unsigned int i = 0; i < userDataContainer->getNumUserObjects(); ++i) { if (PtrHolder* p = dynamic_cast(userDataContainer->getUserObject(i))) - ptrHolder = p; + { + if (std::find(ignoreList.begin(), ignoreList.end(), p->mPtr) == ignoreList.end()) + { + ptrHolder = p; + } + } if (RefnumMarker* r = dynamic_cast(userDataContainer->getUserObject(i))) + { refnumMarkers.push_back(r); + } } } @@ -1056,21 +1061,113 @@ namespace MWRender || (intersectionIndex >= vertexCounter && intersectionIndex < vertexCounter + refnumMarkers[i]->mNumVertices)) { - result.mHitRefnum = refnumMarkers[i]->mRefnum; + auto it = std::find_if( + ignoreList.begin(), ignoreList.end(), [target = refnumMarkers[i]->mRefnum](const auto& ptr) { + return target == ptr.getCellRef().getRefNum(); + }); + + if (it == ignoreList.end()) + { + result.mHitRefnum = refnumMarkers[i]->mRefnum; + } + break; } vertexCounter += refnumMarkers[i]->mNumVertices; } + + if (!result.mHitObject.isEmpty() || result.mHitRefnum.isSet()) + { + result.mHit = true; + result.mHitPointWorld = intersection.getWorldIntersectPoint(); + result.mHitNormalWorld = intersection.getWorldIntersectNormal(); + result.mRatio = intersection.ratio; + } + }; + + if (ignoreList.empty() || intersector->getIntersectionLimit() != osgUtil::LineSegmentIntersector::NO_LIMIT) + { + test(intersector->getFirstIntersection()); + } + else + { + for (const auto& intersection : intersector->getIntersections()) + { + test(intersection); + + if (result.mHit) + { + break; + } + } } return result; } + class IntersectionVisitorWithIgnoreList : public osgUtil::IntersectionVisitor + { + public: + bool skipTransform(osg::Transform& transform) + { + if (mContainsPagedRefs) + return false; + + osg::UserDataContainer* userDataContainer = transform.getUserDataContainer(); + if (!userDataContainer) + return false; + + for (unsigned int i = 0; i < userDataContainer->getNumUserObjects(); ++i) + { + if (PtrHolder* p = dynamic_cast(userDataContainer->getUserObject(i))) + { + if (std::find(mIgnoreList.begin(), mIgnoreList.end(), p->mPtr) != mIgnoreList.end()) + { + return true; + } + } + } + + return false; + } + + void apply(osg::Transform& transform) override + { + if (skipTransform(transform)) + { + return; + } + osgUtil::IntersectionVisitor::apply(transform); + } + + void setIgnoreList(std::span ignoreList) { mIgnoreList = ignoreList; } + void setContainsPagedRefs(bool contains) { mContainsPagedRefs = contains; } + + private: + std::span mIgnoreList; + bool mContainsPagedRefs = false; + }; + osg::ref_ptr RenderingManager::getIntersectionVisitor( - osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors) + osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors, + std::span ignoreList) { if (!mIntersectionVisitor) - mIntersectionVisitor = new osgUtil::IntersectionVisitor; + mIntersectionVisitor = new IntersectionVisitorWithIgnoreList; + + mIntersectionVisitor->setIgnoreList(ignoreList); + mIntersectionVisitor->setContainsPagedRefs(false); + + MWWorld::Scene* worldScene = MWBase::Environment::get().getWorldScene(); + for (const auto& ptr : ignoreList) + { + if (worldScene->isPagedRef(ptr)) + { + mIntersectionVisitor->setContainsPagedRefs(true); + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); + break; + } + } mIntersectionVisitor->setTraversalNumber(mViewer->getFrameStamp()->getFrameNumber()); mIntersectionVisitor->setFrameStamp(mViewer->getFrameStamp()); @@ -1088,16 +1185,16 @@ namespace MWRender return mIntersectionVisitor; } - RenderingManager::RayResult RenderingManager::castRay( - const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors) + RenderingManager::RayResult RenderingManager::castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, + bool ignorePlayer, bool ignoreActors, std::span ignoreList) { osg::ref_ptr intersector( new osgUtil::LineSegmentIntersector(osgUtil::LineSegmentIntersector::MODEL, origin, dest)); intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::LIMIT_NEAREST); - mRootNode->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); + mRootNode->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors, ignoreList)); - return getIntersectionResult(intersector); + return getIntersectionResult(intersector, mIntersectionVisitor, ignoreList); } RenderingManager::RayResult RenderingManager::castCameraToViewportRay( @@ -1117,7 +1214,7 @@ namespace MWRender mViewer->getCamera()->accept(*getIntersectionVisitor(intersector, ignorePlayer, ignoreActors)); - return getIntersectionResult(intersector); + return getIntersectionResult(intersector, mIntersectionVisitor); } void RenderingManager::updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& updated) diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 22ef987c01..e138cc5b7d 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_MWRENDER_RENDERINGMANAGER_H #define OPENMW_MWRENDER_RENDERINGMANAGER_H +#include + #include #include #include @@ -87,6 +89,7 @@ namespace MWRender class StateUpdater; class SharedUniformStateUpdater; class PerViewUniformStateUpdater; + class IntersectionVisitorWithIgnoreList; class EffectManager; class ScreenshotManager; @@ -177,8 +180,8 @@ namespace MWRender float mRatio; }; - RayResult castRay( - const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, bool ignoreActors = false); + RayResult castRay(const osg::Vec3f& origin, const osg::Vec3f& dest, bool ignorePlayer, + bool ignoreActors = false, std::span ignoreList = {}); /// Return the object under the mouse cursor / crosshair position, given by nX and nY normalized screen /// coordinates, where (0,0) is the top left corner. @@ -299,10 +302,10 @@ namespace MWRender const bool mSkyBlending; - osg::ref_ptr getIntersectionVisitor( - osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors); + osg::ref_ptr getIntersectionVisitor(osgUtil::Intersector* intersector, + bool ignorePlayer, bool ignoreActors, std::span ignoreList = {}); - osg::ref_ptr mIntersectionVisitor; + osg::ref_ptr mIntersectionVisitor; osg::ref_ptr mViewer; osg::ref_ptr mRootNode; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 8424076758..a5787e301e 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -99,6 +99,10 @@ namespace return ptr.getClass().getCorrectedModel(ptr); } + // Null node meant to distinguish objects that aren't in the scene from paged objects + // TODO: find a more clever way to make paging exclusion more reliable? + static osg::ref_ptr pagedNode = new SceneUtil::PositionAttitudeTransform; + void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const std::vector& pagedRefs, MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering) { @@ -111,11 +115,6 @@ namespace std::string model = getModel(ptr); const auto rotation = makeDirectNodeRotation(ptr); - // Null node meant to distinguish objects that aren't in the scene from paged objects - // TODO: find a more clever way to make paging exclusion more reliable? - static const osg::ref_ptr pagedNode( - new SceneUtil::PositionAttitudeTransform); - ESM::RefNum refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || !std::binary_search(pagedRefs.begin(), pagedRefs.end(), refnum)) ptr.getClass().insertObjectRendering(ptr, model, rendering); @@ -164,13 +163,13 @@ namespace Misc::Convert::makeBulletQuaternion(ptr.getCellRef().getPosition()), transform.getOrigin()); const auto start = Misc::Convert::toOsg(closedDoorTransform(center + toPoint)); - const auto startPoint = physics.castRay(start, start - osg::Vec3f(0, 0, 1000), ptr, {}, + const auto startPoint = physics.castRay(start, start - osg::Vec3f(0, 0, 1000), { ptr }, {}, MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water); const auto connectionStart = startPoint.mHit ? startPoint.mHitPos : start; const auto end = Misc::Convert::toOsg(closedDoorTransform(center - toPoint)); - const auto endPoint = physics.castRay(end, end - osg::Vec3f(0, 0, 1000), ptr, {}, + const auto endPoint = physics.castRay(end, end - osg::Vec3f(0, 0, 1000), { ptr }, {}, MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water); const auto connectionEnd = endPoint.mHit ? endPoint.mHitPos : end; @@ -274,7 +273,6 @@ namespace namespace MWWorld { - void Scene::removeFromPagedRefs(const Ptr& ptr) { ESM::RefNum refnum = ptr.getCellRef().getRefNum(); @@ -288,6 +286,11 @@ namespace MWWorld } } + bool Scene::isPagedRef(const Ptr& ptr) const + { + return ptr.getRefData().getBaseNode() == pagedNode.get(); + } + void Scene::updateObjectRotation(const Ptr& ptr, RotationOrder order) { const auto rot = makeNodeRotation(ptr, order); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index f3dd377845..fdca9bb87f 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -190,6 +190,8 @@ namespace MWWorld void removeFromPagedRefs(const Ptr& ptr); + bool isPagedRef(const Ptr& ptr) const; + void updateObjectRotation(const Ptr& ptr, RotationOrder order); void updateObjectScale(const Ptr& ptr); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 689edaf62e..e28efbf671 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1817,9 +1817,10 @@ namespace MWWorld } bool World::castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, - bool ignorePlayer, bool ignoreActors) + bool ignorePlayer, bool ignoreActors, std::span ignoreList) { - MWRender::RenderingManager::RayResult rayRes = mRendering->castRay(from, to, ignorePlayer, ignoreActors); + MWRender::RenderingManager::RayResult rayRes + = mRendering->castRay(from, to, ignorePlayer, ignoreActors, ignoreList); res.mHit = rayRes.mHit; res.mHitPos = rayRes.mHitPointWorld; res.mHitNormal = rayRes.mHitNormalWorld; @@ -2598,7 +2599,7 @@ namespace MWWorld collisionTypes |= MWPhysics::CollisionType_Water; } MWPhysics::RayCastingResult result - = mPhysics->castRay(from, to, MWWorld::Ptr(), std::vector(), collisionTypes); + = mPhysics->castRay(from, to, { MWWorld::Ptr() }, std::vector(), collisionTypes); if (!result.mHit) return maxDist; @@ -3064,8 +3065,8 @@ namespace MWWorld actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); // Check for impact, if yes, handle hit, if not, launch projectile - MWPhysics::RayCastingResult result - = mPhysics->castRay(sourcePos, worldPos, actor, targetActors, 0xff, MWPhysics::CollisionType_Projectile); + MWPhysics::RayCastingResult result = mPhysics->castRay( + sourcePos, worldPos, { actor }, targetActors, 0xff, MWPhysics::CollisionType_Projectile); if (result.mHit) MWMechanics::projectileHit(actor, result.mHitObject, bow, projectile, result.mHitPos, attackStrength); else diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index b5d56753b0..4e36419e7f 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -392,7 +392,7 @@ namespace MWWorld const MWPhysics::RayCastingInterface* getRayCasting() const override; bool castRenderingRay(MWPhysics::RayCastingResult& res, const osg::Vec3f& from, const osg::Vec3f& to, - bool ignorePlayer, bool ignoreActors) override; + bool ignorePlayer, bool ignoreActors, std::span ignoreList) override; void setActorCollisionMode(const Ptr& ptr, bool internal, bool external) override; bool isActorCollisionEnabled(const Ptr& ptr) override; diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index 70b09efd90..ea1b8738bc 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -89,6 +89,11 @@ -- radius = 10, -- }) +--- +-- A table of parameters for @{#nearby.castRenderingRay} and @{#nearby.asyncCastRenderingRay} +-- @type CastRenderingRayOptions +-- @field #table ignore A list of @{openmw.core#GameObject} to ignore while doing the ray cast + --- -- Cast ray from one point to another and find the first visual intersection with anything in the scene. -- As opposite to `castRay` can find an intersection with an object without collisions. @@ -97,6 +102,7 @@ -- @function [parent=#nearby] castRenderingRay -- @param openmw.util#Vector3 from Start point of the ray. -- @param openmw.util#Vector3 to End point of the ray. +-- @param #CastRenderingRayOptions -- @return #RayCastingResult --- @@ -105,6 +111,7 @@ -- @param openmw.async#Callback callback The callback to pass the result to (should accept a single argument @{openmw.nearby#RayCastingResult}). -- @param openmw.util#Vector3 from Start point of the ray. -- @param openmw.util#Vector3 to End point of the ray. +-- @param #CastRenderingRayOptions --- -- @type NAVIGATOR_FLAGS From 1523a067c9bfe26df2dd08c7ca7785bfc4d90f79 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 12 Feb 2024 17:32:43 +0100 Subject: [PATCH 054/451] Use concepts and aggregate initialization --- apps/openmw_test_suite/esm3/testsaveload.cpp | 63 +++++++++----------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index 8010e1d7ef..eda1fa963e 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -90,18 +90,17 @@ namespace ESM constexpr std::uint32_t fakeRecordId = fourCC("FAKE"); - template > - struct HasSave : std::false_type - { - }; - template - struct HasSave().save(std::declval()))>> : std::true_type + concept HasSave = requires(T v, ESMWriter& w) { + v.save(w); }; template - auto save(const T& record, ESMWriter& writer) -> std::enable_if_t>::value> + concept NotHasSave = !HasSave; + + template + auto save(const T& record, ESMWriter& writer) { record.save(writer); } @@ -111,8 +110,8 @@ namespace ESM record.save(writer, true); } - template - auto save(const T& record, ESMWriter& writer) -> std::enable_if_t>::value> + template + auto save(const T& record, ESMWriter& writer) { writer.writeComposite(record); } @@ -130,36 +129,29 @@ namespace ESM return stream; } - template > - struct HasLoad : std::false_type + template + concept HasLoad = requires(T v, ESMReader& r) { + v.load(r); }; template - struct HasLoad().load(std::declval()))>> : std::true_type + concept HasLoadWithDelete = requires(T v, ESMReader& r, bool& d) { + v.load(r, d); }; template - auto load(ESMReader& reader, T& record) -> std::enable_if_t>::value> + concept NotHasLoad = !HasLoad && !HasLoadWithDelete; + + template + void load(ESMReader& reader, T& record) { record.load(reader); } - template > - struct HasLoadWithDelete : std::false_type - { - }; - - template - struct HasLoadWithDelete().load(std::declval(), std::declval()))>> - : std::true_type - { - }; - - template - auto load(ESMReader& reader, T& record) -> std::enable_if_t>::value> + template + void load(ESMReader& reader, T& record) { bool deleted = false; record.load(reader, deleted); @@ -171,8 +163,8 @@ namespace ESM record.load(reader, deleted, true); } - template - auto load(ESMReader& reader, T& record) -> std::enable_if_t>::value> + template + void load(ESMReader& reader, T& record) { reader.getComposite(record); } @@ -515,12 +507,13 @@ namespace ESM TEST_P(Esm3SaveLoadRecordTest, aiDataShouldNotChange) { - AIData record; - record.mHello = 1; - record.mFight = 2; - record.mFlee = 3; - record.mAlarm = 4; - record.mServices = 5; + AIData record = { + .mHello = 1, + .mFight = 2, + .mFlee = 3, + .mAlarm = 4, + .mServices = 5, + }; AIData result; saveAndLoadRecord(record, GetParam(), result); From 35448bf0fe863662c22032f9623cada7841503e1 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 12 Feb 2024 20:28:56 +0100 Subject: [PATCH 055/451] Fix crash when passing a non-callback table to a callback argument --- components/lua/asyncpackage.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/lua/asyncpackage.cpp b/components/lua/asyncpackage.cpp index 8316ab2cde..863680ae9e 100644 --- a/components/lua/asyncpackage.cpp +++ b/components/lua/asyncpackage.cpp @@ -24,7 +24,11 @@ namespace LuaUtil Callback Callback::fromLua(const sol::table& t) { - return Callback{ t.raw_get(1), t.raw_get(2).mHiddenData }; + const sol::object& function = t.get_or(1, sol::nil); + const sol::object& asyncPackageId = t.get_or(2, sol::nil); + if (!function.is() || !asyncPackageId.is()) + throw std::domain_error("Expected an async:callback, received a table"); + return Callback{ function.as(), asyncPackageId.as().mHiddenData }; } bool Callback::isLuaCallback(const sol::object& t) From 851e291501fa05514a5f0ad9675b5b506e9fc3a9 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 13 Feb 2024 00:56:14 +0100 Subject: [PATCH 056/451] Simplify and fix the storage subscribe test --- apps/openmw_test_suite/lua/test_storage.cpp | 24 +++++++------- components/lua/asyncpackage.cpp | 36 +++++++++++++-------- components/lua/asyncpackage.hpp | 2 ++ components/lua/luastate.cpp | 2 +- 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_storage.cpp b/apps/openmw_test_suite/lua/test_storage.cpp index a36a527e0c..165737d1ed 100644 --- a/apps/openmw_test_suite/lua/test_storage.cpp +++ b/apps/openmw_test_suite/lua/test_storage.cpp @@ -15,7 +15,7 @@ namespace return lua.safe_script("return " + luaCode).get(); } - TEST(LuaUtilStorageTest, Basic) + TEST(LuaUtilStorageTest, Subscribe) { // Note: LuaUtil::Callback can be used only if Lua is initialized via LuaUtil::LuaState LuaUtil::LuaState luaState{ nullptr, nullptr }; @@ -24,21 +24,21 @@ namespace LuaUtil::LuaStorage storage(mLua); storage.setActive(true); - std::vector callbackCalls; sol::table callbackHiddenData(mLua, sol::create); callbackHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = LuaUtil::ScriptId{}; - sol::table callback(mLua, sol::create); - callback[1] = [&](const std::string& section, const sol::optional& key) { - if (key) - callbackCalls.push_back(section + "_" + *key); - else - callbackCalls.push_back(section + "_*"); - }; - callback[2] = LuaUtil::AsyncPackageId{ nullptr, 0, callbackHiddenData }; + LuaUtil::getAsyncPackageInitializer( + mLua.lua_state(), []() { return 0.0; }, []() { return 0.0; })(callbackHiddenData); + mLua["async"] = LuaUtil::AsyncPackageId{ nullptr, 0, callbackHiddenData }; mLua["mutable"] = storage.getMutableSection("test"); mLua["ro"] = storage.getReadOnlySection("test"); - mLua["ro"]["subscribe"](mLua["ro"], callback); + + mLua.safe_script(R"( + callbackCalls = {} + ro:subscribe(async:callback(function(section, key) + table.insert(callbackCalls, section .. '_' .. (key or '*')) + end)) + )"); mLua.safe_script("mutable:set('x', 5)"); EXPECT_EQ(get(mLua, "mutable:get('x')"), 5); @@ -58,7 +58,7 @@ namespace EXPECT_EQ(get(mLua, "ro:get('x')"), 4); EXPECT_EQ(get(mLua, "ro:get('y')"), 7); - EXPECT_THAT(callbackCalls, ::testing::ElementsAre("test_x", "test_*", "test_*")); + EXPECT_THAT(get(mLua, "table.concat(callbackCalls, ', ')"), "test_x, test_*, test_*"); } TEST(LuaUtilStorageTest, Table) diff --git a/components/lua/asyncpackage.cpp b/components/lua/asyncpackage.cpp index 863680ae9e..6e13406511 100644 --- a/components/lua/asyncpackage.cpp +++ b/components/lua/asyncpackage.cpp @@ -24,13 +24,32 @@ namespace LuaUtil Callback Callback::fromLua(const sol::table& t) { - const sol::object& function = t.get_or(1, sol::nil); - const sol::object& asyncPackageId = t.get_or(2, sol::nil); + const sol::object& function = t.raw_get(1); + const sol::object& asyncPackageId = t.raw_get(2); if (!function.is() || !asyncPackageId.is()) throw std::domain_error("Expected an async:callback, received a table"); return Callback{ function.as(), asyncPackageId.as().mHiddenData }; } + sol::table Callback::makeMetatable(lua_State* L) + { + sol::table callbackMeta = sol::table::create(L); + callbackMeta[sol::meta_function::call] = [](const sol::table& callback, sol::variadic_args va) { + return Callback::fromLua(callback).call(sol::as_args(va)); + }; + callbackMeta[sol::meta_function::to_string] = [] { return "Callback"; }; + callbackMeta[sol::meta_function::metatable] = false; + callbackMeta["isCallback"] = true; + return callbackMeta; + } + sol::table Callback::make(const AsyncPackageId& asyncId, sol::main_protected_function fn, sol::table metatable) + { + sol::table c = sol::table::create(fn.lua_state(), 2); + c.raw_set(1, std::move(fn), 2, asyncId); + c[sol::metatable_key] = metatable; + return c; + } + bool Callback::isLuaCallback(const sol::object& t) { if (!t.is()) @@ -73,18 +92,9 @@ namespace LuaUtil TimerType::GAME_TIME, gameTimeFn() + delay, asyncId.mScriptId, std::move(callback)); }; - sol::table callbackMeta = sol::table::create(L); - callbackMeta[sol::meta_function::call] = [](const sol::table& callback, sol::variadic_args va) { - return Callback::fromLua(callback).call(sol::as_args(va)); - }; - callbackMeta[sol::meta_function::to_string] = [] { return "Callback"; }; - callbackMeta[sol::meta_function::metatable] = false; - callbackMeta["isCallback"] = true; + sol::table callbackMeta = Callback::makeMetatable(L); api["callback"] = [callbackMeta](const AsyncPackageId& asyncId, sol::main_protected_function fn) -> sol::table { - sol::table c = sol::table::create(fn.lua_state(), 2); - c.raw_set(1, std::move(fn), 2, asyncId); - c[sol::metatable_key] = callbackMeta; - return c; + return Callback::make(asyncId, fn, callbackMeta); }; auto initializer = [](sol::table hiddenData) { diff --git a/components/lua/asyncpackage.hpp b/components/lua/asyncpackage.hpp index e8d5f04271..7e9c36de09 100644 --- a/components/lua/asyncpackage.hpp +++ b/components/lua/asyncpackage.hpp @@ -24,6 +24,8 @@ namespace LuaUtil static bool isLuaCallback(const sol::object&); static Callback fromLua(const sol::table&); + static sol::table makeMetatable(lua_State* L); + static sol::table make(const AsyncPackageId& asyncId, sol::main_protected_function fn, sol::table metatable); bool isValid() const { return mHiddenData[ScriptsContainer::sScriptIdKey] != sol::nil; } diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 0a350a2d9f..13e2208b68 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -377,7 +377,7 @@ namespace LuaUtil sol::protected_function_result LuaState::throwIfError(sol::protected_function_result&& res) { if (!res.valid() && static_cast(res.get_type()) == LUA_TSTRING) - throw std::runtime_error("Lua error: " + res.get()); + throw std::runtime_error(std::string("Lua error: ") += res.get().what()); else return std::move(res); } From 4ffcb5b197e4707dadbb3491bf0c8d59a7b24db0 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 13 Feb 2024 00:59:39 +0100 Subject: [PATCH 057/451] Prevent deepContentCopy corrupting non-plain tables --- files/data/openmw_aux/ui.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/openmw_aux/ui.lua b/files/data/openmw_aux/ui.lua index 0abd3f2f6d..cadae2a033 100644 --- a/files/data/openmw_aux/ui.lua +++ b/files/data/openmw_aux/ui.lua @@ -24,7 +24,7 @@ function aux_ui.deepLayoutCopy(layout) for k, v in pairs(layout) do if k == 'content' then result[k] = deepContentCopy(v) - elseif type(v) == 'table' then + elseif type(v) == 'table' and getmetatable(v) == nil then result[k] = aux_ui.deepLayoutCopy(v) else result[k] = v From a95a4c19fcac54b03763373a0d80923425b592ab Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Mon, 12 Feb 2024 20:06:07 -0600 Subject: [PATCH 058/451] Added fixes for several doc files --- .../installation/install-game-files.rst | 36 ++++++++++--------- .../lua-scripting/interface_controls.rst | 2 +- .../lua-scripting/setting_renderers.rst | 2 +- .../modding/custom-shader-effects.rst | 2 +- .../modding/settings/postprocessing.rst | 2 ++ 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/docs/source/manuals/installation/install-game-files.rst b/docs/source/manuals/installation/install-game-files.rst index 6da5d3d55a..8a7a594691 100644 --- a/docs/source/manuals/installation/install-game-files.rst +++ b/docs/source/manuals/installation/install-game-files.rst @@ -101,18 +101,7 @@ Assuming you used the filepath above, your ``.esm`` files will be located in ``~ You can now run the OpenMW launcher, and from there run the installation wizard. Point it to your ``Morrowind.esm`` in the folder you extracted it to, and follow the instructions. ------ -Steam ------ - -Windows -------- - -Windows users can download Morrowind through Steam. -Afterwards, you can point OpenMW to the Steam install location at -``C:\Program Files\Steam\SteamApps\common\Morrowind\Data Files\`` -and find ``Morrowind.esm`` there. - +--------------------- XBox Game Pass for PC --------------------- @@ -125,8 +114,21 @@ option and the app will prompt you to move the Morrowind files to a new folder. Once done you can find ``Morrowind.esm`` in the folder you chose. -macOS ----- +Steam +----- + +Windows +^^^^^^^ + +Windows users can download Morrowind through Steam. +Afterwards, you can point OpenMW to the Steam install location at +``C:\Program Files\Steam\SteamApps\common\Morrowind\Data Files\`` +and find ``Morrowind.esm`` there. + + +macOS +^^^^^ If you are running macOS, you can also download Morrowind through Steam: @@ -151,9 +153,9 @@ If you are running macOS, you can also download Morrowind through Steam: ``~/Library/Application Support/Steam/steamapps/common/The Elder Scrolls III - Morrowind/Data Files/`` Linux ------ +^^^^^ Debian/Ubuntu - using "Steam Proton" & "OpenMW launcher". ---------------------------------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #. Install Steam from "Ubuntu Software" Center #. Enable Proton (basically WINE under the hood). This is done in the Steam client menu drop down. Select, "Steam | Settings" then in the "SteamPlay" section check the box next to "enable steam play for all other titles" #. Now Morrowind should be selectable in your game list (as long as you own it). You can install it like any other game, choose to install it and remember the directory path of the location you pick. @@ -161,10 +163,10 @@ Debian/Ubuntu - using "Steam Proton" & "OpenMW launcher". #. Launch "OpenMW launcher" and follow the setup wizard, when asked, point it at the location you installed Morrowind to, we will be looking for the directory that contains the Morrowing.esm file, for example '/steam library/steamapps/common/Morrowind/Data Files/'. #. Everything should now be in place, click that big "PLAY" button and fire up OpenMW. -Nb. Bloodmoon.esm needs to be below Tribunal.esm in your datafiles list, if you dont have the right order a red "!" will apear next to the filename in the datafiles section of the OpenMW launcher, just drag bloodmoon below tribunal to fix it. +Note, Bloodmoon.esm needs to be below Tribunal.esm in your datafiles list, if you don't have the right order a red "!" will apear next to the filename in the datafiles section of the OpenMW launcher, just drag bloodmoon below tribunal to fix it. Wine ----- +~~~~ Users of other platforms running Wine can run Steam within it and find ``Morrowind.esm`` at diff --git a/docs/source/reference/lua-scripting/interface_controls.rst b/docs/source/reference/lua-scripting/interface_controls.rst index c4b1709f59..f8e8fd0d4e 100644 --- a/docs/source/reference/lua-scripting/interface_controls.rst +++ b/docs/source/reference/lua-scripting/interface_controls.rst @@ -4,5 +4,5 @@ Interface Controls .. include:: version.rst .. raw:: html - :file: generated_html/scripts_omw_playercontrols.html + :file: generated_html/scripts_omw_input_playercontrols.html diff --git a/docs/source/reference/lua-scripting/setting_renderers.rst b/docs/source/reference/lua-scripting/setting_renderers.rst index 7f40eb08bd..eac35f9187 100644 --- a/docs/source/reference/lua-scripting/setting_renderers.rst +++ b/docs/source/reference/lua-scripting/setting_renderers.rst @@ -128,7 +128,7 @@ Table with the following optional fields: - Disables changing the setting from the UI inputBinding ------ +------------ Allows the user to bind inputs to an action or trigger diff --git a/docs/source/reference/modding/custom-shader-effects.rst b/docs/source/reference/modding/custom-shader-effects.rst index 60a306a97a..f0a517f9f1 100644 --- a/docs/source/reference/modding/custom-shader-effects.rst +++ b/docs/source/reference/modding/custom-shader-effects.rst @@ -61,7 +61,7 @@ This effect is used to imitate effects such as refraction and heat distortion. A diffuse slot to a material and add uv scrolling. The red and green channels of the texture are used to offset the final scene texture. Blue and alpha channels are ignored. -To use this feature the :ref:`post processing` setting must be enabled. +To use this feature the :ref:`post processing ` setting must be enabled. This setting can either be activated in the OpenMW launcher, in-game, or changed in `settings.cfg`: :: diff --git a/docs/source/reference/modding/settings/postprocessing.rst b/docs/source/reference/modding/settings/postprocessing.rst index b4b2f5a8d4..ed876c88d6 100644 --- a/docs/source/reference/modding/settings/postprocessing.rst +++ b/docs/source/reference/modding/settings/postprocessing.rst @@ -1,6 +1,8 @@ Post Processing Settings ######################## +.. _Post Processing: + enabled ------- From 6486f3f2cff1b70f53197e8da3dd120cc84a5555 Mon Sep 17 00:00:00 2001 From: Epoch Date: Tue, 13 Feb 2024 09:18:18 +0000 Subject: [PATCH 059/451] Add option to use camera as sound listener --- AUTHORS.md | 1 + CHANGELOG.md | 1 + apps/launcher/settingspage.cpp | 4 ++++ apps/launcher/ui/settingspage.ui | 10 +++++++++ apps/openmw/mwrender/camera.cpp | 9 ++++++-- apps/openmw/mwrender/camera.hpp | 2 ++ apps/openmw/mwworld/worldimp.cpp | 22 +++++++++++-------- components/settings/categories/sound.hpp | 1 + .../reference/modding/settings/sound.rst | 13 +++++++++++ files/lang/launcher_de.ts | 8 +++++++ files/lang/launcher_fr.ts | 8 +++++++ files/lang/launcher_ru.ts | 8 +++++++ files/settings-default.cfg | 3 +++ 13 files changed, 79 insertions(+), 11 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 8873113da2..4d03fba227 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -79,6 +79,7 @@ Programmers Eduard Cot (trombonecot) Eli2 Emanuel Guével (potatoesmaster) + Epoch Eris Caffee (eris) eroen escondida diff --git a/CHANGELOG.md b/CHANGELOG.md index 93eb959e18..c2a8c8e34d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -146,6 +146,7 @@ Feature #5173: Support for NiFogProperty Feature #5492: Let rain and snow collide with statics Feature #5926: Refraction based on water depth + Feature #5944: Option to use camera as sound listener Feature #6149: Dehardcode Lua API_REVISION Feature #6152: Playing music via lua scripts Feature #6188: Specular lighting from point light sources diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index c274b75f79..0b5f542888 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -294,6 +294,7 @@ bool Launcher::SettingsPage::loadSettings() hrtfProfileSelectorComboBox->setCurrentIndex(hrtfProfileIndex); } } + loadSettingBool(Settings::sound().mCameraListener, *cameraListenerCheckBox); } // Interface Changes @@ -490,6 +491,9 @@ void Launcher::SettingsPage::saveSettings() Settings::sound().mHrtf.set(hrtfProfileSelectorComboBox->currentText().toStdString()); else Settings::sound().mHrtf.set({}); + + const bool cCameraListener = cameraListenerCheckBox->checkState() != Qt::Unchecked; + Settings::sound().mCameraListener.set(cCameraListener); } // Interface Changes diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 2d06c1802e..a30309bd1b 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -1320,6 +1320,16 @@ + + + + In third-person view, use the camera as the sound listener instead of the player character. + + + Use the camera as the sound listener + + + diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 86d5699d75..1c163a6701 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -109,8 +109,7 @@ namespace MWRender void Camera::updateCamera(osg::Camera* cam) { - osg::Quat orient = osg::Quat(mRoll + mExtraRoll, osg::Vec3d(0, 1, 0)) - * osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1)); + osg::Quat orient = getOrient(); osg::Vec3d forward = orient * osg::Vec3d(0, 1, 0); osg::Vec3d up = orient * osg::Vec3d(0, 0, 1); @@ -209,6 +208,12 @@ namespace MWRender mPosition = focal + offset; } + osg::Quat Camera::getOrient() const + { + return osg::Quat(mRoll + mExtraRoll, osg::Vec3d(0, 1, 0)) * osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) + * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1)); + } + void Camera::setMode(Mode newMode, bool force) { if (mMode == newMode) diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index c6500160fd..e09a265293 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -72,6 +72,8 @@ namespace MWRender void setExtraYaw(float angle) { mExtraYaw = angle; } void setExtraRoll(float angle) { mExtraRoll = angle; } + osg::Quat getOrient() const; + /// @param Force view mode switch, even if currently not allowed by the animation. void toggleViewMode(bool force = false); bool toggleVanityMode(bool enable); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 689edaf62e..2a776ce051 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1738,23 +1738,27 @@ namespace MWWorld void World::updateSoundListener() { - osg::Vec3f cameraPosition = mRendering->getCamera()->getPosition(); + const MWRender::Camera* camera = mRendering->getCamera(); const auto& player = getPlayerPtr(); const ESM::Position& refpos = player.getRefData().getPosition(); - osg::Vec3f listenerPos; + osg::Vec3f listenerPos, up, forward; + osg::Quat listenerOrient; - if (isFirstPerson()) - listenerPos = cameraPosition; + if (isFirstPerson() || Settings::sound().mCameraListener) + listenerPos = camera->getPosition(); else listenerPos = refpos.asVec3() + osg::Vec3f(0, 0, 1.85f * mPhysics->getHalfExtents(player).z()); - osg::Quat listenerOrient = osg::Quat(refpos.rot[1], osg::Vec3f(0, -1, 0)) - * osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1)); + if (isFirstPerson() || Settings::sound().mCameraListener) + listenerOrient = camera->getOrient(); + else + listenerOrient = osg::Quat(refpos.rot[1], osg::Vec3f(0, -1, 0)) + * osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1)); - osg::Vec3f forward = listenerOrient * osg::Vec3f(0, 1, 0); - osg::Vec3f up = listenerOrient * osg::Vec3f(0, 0, 1); + forward = listenerOrient * osg::Vec3f(0, 1, 0); + up = listenerOrient * osg::Vec3f(0, 0, 1); - bool underwater = isUnderwater(player.getCell(), cameraPosition); + bool underwater = isUnderwater(player.getCell(), camera->getPosition()); MWBase::Environment::get().getSoundManager()->setListenerPosDir(listenerPos, forward, up, underwater); } diff --git a/components/settings/categories/sound.hpp b/components/settings/categories/sound.hpp index 995bce2a58..8398a38c55 100644 --- a/components/settings/categories/sound.hpp +++ b/components/settings/categories/sound.hpp @@ -23,6 +23,7 @@ namespace Settings SettingValue mBufferCacheMax{ mIndex, "Sound", "buffer cache max", makeMaxSanitizerInt(1) }; SettingValue mHrtfEnable{ mIndex, "Sound", "hrtf enable" }; SettingValue mHrtf{ mIndex, "Sound", "hrtf" }; + SettingValue mCameraListener{ mIndex, "Sound", "camera listener" }; }; } diff --git a/docs/source/reference/modding/settings/sound.rst b/docs/source/reference/modding/settings/sound.rst index 7a5718735c..dbcad65d0d 100644 --- a/docs/source/reference/modding/settings/sound.rst +++ b/docs/source/reference/modding/settings/sound.rst @@ -127,3 +127,16 @@ Allowed values for this field are enumerated in openmw.log file is an HRTF enabl The default value is empty, which uses the default profile. This setting can be controlled in the Settings tab of the launcher. + +camera listener +--------------- + +:Type: boolean +:Range: True/False +:Default: False + +When true, uses the camera position and direction for audio instead of the player position. +This makes audio in third person sound relative to camera instead of the player. +False is vanilla Morrowind behaviour. + +This setting can be controlled in the Settings tab of the launcher. \ No newline at end of file diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 1c23f90425..5d785267a8 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -1447,5 +1447,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Lights minimum interior brightness + + In third-person view, use the camera as the sound listener instead of the player character. + + + + Use the camera as the sound listener + + diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index f604044368..58232efb7c 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -1447,5 +1447,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Lights minimum interior brightness + + In third-person view, use the camera as the sound listener instead of the player character. + + + + Use the camera as the sound listener + + diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 844e36898c..1a0ea8d2cf 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -1462,5 +1462,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Lights minimum interior brightness Минимальный уровень освещения в помещениях + + In third-person view, use the camera as the sound listener instead of the player character. + Использовать в виде от третьего лица положение камеры, а не персонажа игрока для прослушивания звуков. + + + Use the camera as the sound listener + Использовать камеру как слушателя + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 4a90a46cc5..1caf03c123 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -608,6 +608,9 @@ hrtf enable = -1 # Specifies which HRTF to use when HRTF is used. Blank means use the default. hrtf = +# Specifies whether to use camera as audio listener +camera listener = false + [Video] # Resolution of the OpenMW window or screen. From 8f88838ff515baafa0798f55a9b063259f253dc6 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 13 Feb 2024 19:30:32 +0100 Subject: [PATCH 060/451] Remove dead code --- apps/openmw/mwlua/animationbindings.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index d024c41307..5c4ccf7212 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -21,24 +21,6 @@ #include "animationbindings.hpp" #include -namespace MWLua -{ - struct AnimationGroup; - struct TextKeyCallback; -} - -namespace sol -{ - template <> - struct is_automagical : std::false_type - { - }; - template <> - struct is_automagical> : std::false_type - { - }; -} - namespace MWLua { using BlendMask = MWRender::Animation::BlendMask; From aa4303fc385fd35fa63997290153835c9c7d2cf8 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 12 Feb 2024 22:25:12 +0100 Subject: [PATCH 061/451] Fix crash when throwing in index meta methods --- CMakeLists.txt | 1 + components/lua/luastate.hpp | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b1b18e41e..3a69f28c75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -477,6 +477,7 @@ if (NOT WIN32) endif() # C++ library binding to Lua +add_compile_definitions(SOL_ALL_SAFETIES_ON) set(SOL_INCLUDE_DIR ${OpenMW_SOURCE_DIR}/extern/sol3) set(SOL_CONFIG_DIR ${OpenMW_SOURCE_DIR}/extern/sol_config) diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index aea1e32590..f14fc44867 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -232,16 +232,36 @@ namespace LuaUtil } } + // work around for a (likely) sol3 bug + // when the index meta method throws, simply calling table.get crashes instead of re-throwing the error + template + sol::object safe_get(const sol::table& table, const Key& key) + { + auto index = table.traverse_raw_get>( + sol::metatable_key, sol::meta_function::index); + if (index) + { + sol::protected_function_result result = index.value()(table, key); + if (result.valid()) + return result.get(); + else + throw result.get(); + } + else + return table.raw_get(key); + } + // getFieldOrNil(table, "a", "b", "c") returns table["a"]["b"]["c"] or nil if some of the fields doesn't exist. template sol::object getFieldOrNil(const sol::object& table, std::string_view first, const Str&... str) { if (!table.is()) return sol::nil; + sol::object value = safe_get(table.as(), first); if constexpr (sizeof...(str) == 0) - return table.as()[first]; + return value; else - return getFieldOrNil(table.as()[first], str...); + return getFieldOrNil(value, str...); } // String representation of a Lua object. Should be used for debugging/logging purposes only. From 550659c2d936cb658c676f461effa55f32934e89 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 12 Feb 2024 22:40:07 +0100 Subject: [PATCH 062/451] Fix loadVFS error handling --- components/lua/luastate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 13e2208b68..3cf6378f2f 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -376,7 +376,7 @@ namespace LuaUtil sol::protected_function_result LuaState::throwIfError(sol::protected_function_result&& res) { - if (!res.valid() && static_cast(res.get_type()) == LUA_TSTRING) + if (!res.valid()) throw std::runtime_error(std::string("Lua error: ") += res.get().what()); else return std::move(res); @@ -397,7 +397,7 @@ namespace LuaUtil std::string fileContent(std::istreambuf_iterator(*mVFS->get(path)), {}); sol::load_result res = mSol.load(fileContent, path, sol::load_mode::text); if (!res.valid()) - throw std::runtime_error("Lua error: " + res.get()); + throw std::runtime_error(std::string("Lua error: ") += res.get().what()); return res; } From 08b7ee8a44f40fc50621495ad29ed51f46928954 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 12 Feb 2024 22:54:35 +0100 Subject: [PATCH 063/451] Test LuaUtil::safeGet preventing crash --- apps/openmw_test_suite/lua/test_lua.cpp | 12 +++++++++++- components/lua/luastate.hpp | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_lua.cpp b/apps/openmw_test_suite/lua/test_lua.cpp index 90c987522d..a669a3c670 100644 --- a/apps/openmw_test_suite/lua/test_lua.cpp +++ b/apps/openmw_test_suite/lua/test_lua.cpp @@ -51,6 +51,9 @@ return { } )X"); + TestingOpenMW::VFSTestFile metaIndexErrorFile( + "return setmetatable({}, { __index = function(t, key) error('meta index error') end })"); + std::string genBigScript() { std::stringstream buf; @@ -70,7 +73,7 @@ return { { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { "aaa/counter.lua", &counterFile }, { "bbb/tests.lua", &testsFile }, { "invalid.lua", &invalidScriptFile }, { "big.lua", &bigScriptFile }, - { "requireBig.lua", &requireBigScriptFile } }); + { "requireBig.lua", &requireBigScriptFile }, { "metaIndexError.lua", &metaIndexErrorFile } }); LuaUtil::ScriptsConfiguration mCfg; LuaUtil::LuaState mLua{ mVFS.get(), &mCfg }; @@ -223,4 +226,11 @@ return { // At this moment all instances of the script should be garbage-collected. EXPECT_LT(memWithoutScript, memWithScript); } + + TEST_F(LuaStateTest, SafeIndexMetamethod) + { + sol::table t = mLua.runInNewSandbox("metaIndexError.lua"); + // without safe get we crash here + EXPECT_ERROR(LuaUtil::safeGet(t, "any key"), "meta index error"); + } } diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index f14fc44867..509b5e16e1 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -235,7 +235,7 @@ namespace LuaUtil // work around for a (likely) sol3 bug // when the index meta method throws, simply calling table.get crashes instead of re-throwing the error template - sol::object safe_get(const sol::table& table, const Key& key) + sol::object safeGet(const sol::table& table, const Key& key) { auto index = table.traverse_raw_get>( sol::metatable_key, sol::meta_function::index); @@ -257,7 +257,7 @@ namespace LuaUtil { if (!table.is()) return sol::nil; - sol::object value = safe_get(table.as(), first); + sol::object value = safeGet(table.as(), first); if constexpr (sizeof...(str) == 0) return value; else From d6f112aef295734913de09eca12f0a66147d4c8d Mon Sep 17 00:00:00 2001 From: Anton Uramer Date: Tue, 13 Feb 2024 12:35:28 +0100 Subject: [PATCH 064/451] Revert Lua sol safeties flag --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a69f28c75..7b1b18e41e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -477,7 +477,6 @@ if (NOT WIN32) endif() # C++ library binding to Lua -add_compile_definitions(SOL_ALL_SAFETIES_ON) set(SOL_INCLUDE_DIR ${OpenMW_SOURCE_DIR}/extern/sol3) set(SOL_CONFIG_DIR ${OpenMW_SOURCE_DIR}/extern/sol_config) From 91e7eebefb2c0d9847d67258da27f0d834fa4f46 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 14 Feb 2024 13:32:39 +0000 Subject: [PATCH 065/451] Clarify interaction between clamp lighting and terrain --- docs/source/reference/modding/settings/shaders.rst | 4 ++-- files/settings-default.cfg | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 8bd152857f..121ecef2b1 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -38,8 +38,8 @@ clamp lighting :Default: True Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). -Only affects objects drawn with shaders (see :ref:`force shaders` option). -Always affects terrain. +Only affects objects drawn with shaders (see :ref:`force shaders` option) as objects drawn without shaders always have clamped lighting. +When disabled, terrain is always drawn with shaders to prevent seams between tiles that are and that aren't. Leaving this option at its default makes the lighting compatible with Morrowind's fixed-function method, but the lighting may appear dull and there might be colour shifts. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 5f68f055d4..4fc632f497 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -420,7 +420,8 @@ force shaders = false force per pixel lighting = false # Restrict the amount of lighting that an object can receive to a maximum of (1,1,1). -# Only affects objects that render with shaders (see 'force shaders' option). Always affects terrain. +# Only affects objects that render with shaders (see 'force shaders' option). +# When disabled, terrain is always drawn with shaders to prevent seams between tiles that are and that aren't. # Setting this option to 'true' results in fixed-function compatible lighting, but the lighting # may appear 'dull' and there might be color shifts. # Setting this option to 'false' results in more realistic lighting. From b31664a78ff10a97c9f59993985bb660e4301b86 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Mon, 1 Jan 2024 12:47:01 -0600 Subject: [PATCH 066/451] Fix(CS): Scale actors according to their race's stats --- CHANGELOG.md | 1 + apps/opencs/model/world/actoradapter.cpp | 5 +++++ apps/opencs/model/world/actoradapter.hpp | 2 ++ apps/opencs/view/render/actor.cpp | 14 +++++++++++++- apps/opencs/view/render/actor.hpp | 3 ++- 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c473f068dd..b649ebcb90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -133,6 +133,7 @@ Bug #7724: Guards don't help vs werewolves Bug #7733: Launcher shows incorrect data paths when there's two plugins with the same name Bug #7742: Governing attribute training limit should use the modified attribute + Bug #7753: Editor: Actors Don't Scale According to Their Race Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive Bug #7765: OpenMW-CS: Touch Record option is broken diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 37aaf08445..e4b577480d 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -163,6 +163,11 @@ namespace CSMWorld return it->second.first; } + const ESM::RefId& ActorAdapter::ActorData::getActorRaceName() const + { + return mRaceData->getId(); + } + bool ActorAdapter::ActorData::hasDependency(const ESM::RefId& id) const { return mDependencies.find(id) != mDependencies.end(); diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index 9747f448ae..5ab4da2e58 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -96,6 +96,8 @@ namespace CSMWorld std::string getSkeleton() const; /// Retrieves the associated actor part ESM::RefId getPart(ESM::PartReferenceType index) const; + + const ESM::RefId& getActorRaceName() const; /// Checks if the actor has a data dependency bool hasDependency(const ESM::RefId& id) const; diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index d1bfac0ec6..cfa06012ff 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,7 @@ #include #include +#include "../../model/world/columns.hpp" #include "../../model/world/data.hpp" namespace CSVRender @@ -29,7 +31,7 @@ namespace CSVRender Actor::Actor(const ESM::RefId& id, CSMWorld::Data& data) : mId(id) , mData(data) - , mBaseNode(new osg::Group()) + , mBaseNode(new osg::PositionAttitudeTransform()) , mSkeleton(nullptr) { mActorData = mData.getActorAdapter()->getActorData(mId); @@ -60,6 +62,16 @@ namespace CSVRender // Attach parts to skeleton loadBodyParts(); + + const CSMWorld::IdCollection& races = mData.getRaces(); + const auto& targetRace = races.getRecord(mActorData->getActorRaceName()).get().mData; + osg::Vec3d scale; + + mActorData.get()->isFemale() + ? scale = osg::Vec3(targetRace.mFemaleWeight, targetRace.mFemaleWeight, targetRace.mFemaleHeight) + : scale = osg::Vec3(targetRace.mMaleWeight, targetRace.mMaleWeight, targetRace.mMaleHeight); + + mBaseNode->setScale(scale); } else { diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index 86c7e7ff2d..a9cc34b00d 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -59,7 +60,7 @@ namespace CSVRender CSMWorld::Data& mData; CSMWorld::ActorAdapter::ActorDataPtr mActorData; - osg::ref_ptr mBaseNode; + osg::ref_ptr mBaseNode; SceneUtil::Skeleton* mSkeleton; SceneUtil::NodeMapVisitor::NodeMap mNodeMap; }; From 049550d73ef927fd76b596cef99fd2e483912846 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Mon, 1 Jan 2024 21:00:00 -0600 Subject: [PATCH 067/451] Cleanup(Actoradapter.cpp): Create new struct for race stats, use std::pair instead --- apps/opencs/model/world/actoradapter.cpp | 18 ++++++++++++---- apps/opencs/model/world/actoradapter.hpp | 27 ++++++++++++++++++++++-- apps/opencs/view/render/actor.cpp | 10 ++------- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index e4b577480d..f3c2161c73 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -67,6 +67,11 @@ namespace CSMWorld return mMaleParts[ESM::getMeshPart(index)]; } + const std::pair& ActorAdapter::RaceData::getRaceAttributes(bool isFemale) + { + return isFemale == true ? mHeightsWeights.mFemaleAttributes : mHeightsWeights.mMaleAttributes; + } + bool ActorAdapter::RaceData::hasDependency(const ESM::RefId& id) const { return mDependencies.find(id) != mDependencies.end(); @@ -90,10 +95,11 @@ namespace CSMWorld mDependencies.emplace(id); } - void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, bool isBeast) + void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, const BodyAttributes& raceStats, bool isBeast) { mId = id; mIsBeast = isBeast; + mHeightsWeights = raceStats; for (auto& str : mFemaleParts) str = ESM::RefId(); for (auto& str : mMaleParts) @@ -163,9 +169,9 @@ namespace CSMWorld return it->second.first; } - const ESM::RefId& ActorAdapter::ActorData::getActorRaceName() const + const std::pair& ActorAdapter::ActorData::getRaceHeightWeight() const { - return mRaceData->getId(); + return mRaceData->getRaceAttributes(isFemale()); } bool ActorAdapter::ActorData::hasDependency(const ESM::RefId& id) const @@ -509,7 +515,11 @@ namespace CSMWorld } auto& race = raceRecord.get(); - data->reset_data(id, race.mData.mFlags & ESM::Race::Beast); + + BodyAttributes scaleStats = BodyAttributes( + race.mData.mMaleHeight, race.mData.mMaleWeight, race.mData.mFemaleHeight, race.mData.mFemaleWeight); + + data->reset_data(id, scaleStats, race.mData.mFlags & ESM::Race::Beast); // Setup body parts for (int i = 0; i < mBodyParts.getSize(); ++i) diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index 5ab4da2e58..21298d33df 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -41,6 +41,25 @@ namespace CSMWorld /// Tracks unique strings using RefIdSet = std::unordered_set; + class BodyAttributes + { + public: + std::pair mMaleAttributes; + std::pair mFemaleAttributes; + + BodyAttributes() + : mMaleAttributes(std::make_pair(1.0f, 1.0f)) + , mFemaleAttributes(std::make_pair(1.0f, 1.0f)) + { + } + + BodyAttributes(float maleHeight, float maleWeight, float femaleHeight, float femaleWeight) + { + mMaleAttributes = std::make_pair(maleHeight, maleWeight); + mFemaleAttributes = std::make_pair(femaleHeight, femaleWeight); + } + }; + /// Contains base race data shared between actors class RaceData { @@ -57,6 +76,8 @@ namespace CSMWorld const ESM::RefId& getFemalePart(ESM::PartReferenceType index) const; /// Retrieves the associated body part const ESM::RefId& getMalePart(ESM::PartReferenceType index) const; + + const std::pair& getRaceAttributes(bool isFemale); /// Checks if the race has a data dependency bool hasDependency(const ESM::RefId& id) const; @@ -67,7 +88,8 @@ namespace CSMWorld /// Marks an additional dependency void addOtherDependency(const ESM::RefId& id); /// Clears parts and dependencies - void reset_data(const ESM::RefId& raceId, bool isBeast = false); + void reset_data( + const ESM::RefId& raceId, const BodyAttributes& raceStats = BodyAttributes(), bool isBeast = false); private: bool handles(ESM::PartReferenceType type) const; @@ -75,6 +97,7 @@ namespace CSMWorld bool mIsBeast; RacePartList mFemaleParts; RacePartList mMaleParts; + BodyAttributes mHeightsWeights; RefIdSet mDependencies; }; using RaceDataPtr = std::shared_ptr; @@ -97,7 +120,7 @@ namespace CSMWorld /// Retrieves the associated actor part ESM::RefId getPart(ESM::PartReferenceType index) const; - const ESM::RefId& getActorRaceName() const; + const std::pair& getRaceHeightWeight() const; /// Checks if the actor has a data dependency bool hasDependency(const ESM::RefId& id) const; diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index cfa06012ff..abe7b910de 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -63,15 +63,9 @@ namespace CSVRender // Attach parts to skeleton loadBodyParts(); - const CSMWorld::IdCollection& races = mData.getRaces(); - const auto& targetRace = races.getRecord(mActorData->getActorRaceName()).get().mData; - osg::Vec3d scale; + const std::pair attributes = mActorData.get()->getRaceHeightWeight(); - mActorData.get()->isFemale() - ? scale = osg::Vec3(targetRace.mFemaleWeight, targetRace.mFemaleWeight, targetRace.mFemaleHeight) - : scale = osg::Vec3(targetRace.mMaleWeight, targetRace.mMaleWeight, targetRace.mMaleHeight); - - mBaseNode->setScale(scale); + mBaseNode->setScale(osg::Vec3d(attributes.second, attributes.second, attributes.first)); } else { From 98ad059806e1ec7d8fb3ff15d55658d2640c3930 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 2 Jan 2024 16:08:25 -0600 Subject: [PATCH 068/451] Cleanup(actoradapter): Use more explicit names & vec2 for racial height/weight --- apps/opencs/model/world/actoradapter.cpp | 14 ++++++------- apps/opencs/model/world/actoradapter.hpp | 26 ++++++++++++------------ apps/opencs/view/render/actor.cpp | 4 ++-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index f3c2161c73..40bfe17989 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -67,9 +67,9 @@ namespace CSMWorld return mMaleParts[ESM::getMeshPart(index)]; } - const std::pair& ActorAdapter::RaceData::getRaceAttributes(bool isFemale) + const osg::Vec2f& ActorAdapter::RaceData::getHeightWeight(bool isFemale) { - return isFemale == true ? mHeightsWeights.mFemaleAttributes : mHeightsWeights.mMaleAttributes; + return isFemale ? mHeightsWeights.mFemaleHeightWeight : mHeightsWeights.mMaleHeightWeight; } bool ActorAdapter::RaceData::hasDependency(const ESM::RefId& id) const @@ -95,7 +95,7 @@ namespace CSMWorld mDependencies.emplace(id); } - void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, const BodyAttributes& raceStats, bool isBeast) + void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, const HeightsWeights& raceStats, bool isBeast) { mId = id; mIsBeast = isBeast; @@ -169,9 +169,9 @@ namespace CSMWorld return it->second.first; } - const std::pair& ActorAdapter::ActorData::getRaceHeightWeight() const + const osg::Vec2f& ActorAdapter::ActorData::getRaceHeightWeight() const { - return mRaceData->getRaceAttributes(isFemale()); + return mRaceData->getHeightWeight(isFemale()); } bool ActorAdapter::ActorData::hasDependency(const ESM::RefId& id) const @@ -516,8 +516,8 @@ namespace CSMWorld auto& race = raceRecord.get(); - BodyAttributes scaleStats = BodyAttributes( - race.mData.mMaleHeight, race.mData.mMaleWeight, race.mData.mFemaleHeight, race.mData.mFemaleWeight); + HeightsWeights scaleStats = HeightsWeights(osg::Vec2f(race.mData.mMaleWeight, race.mData.mMaleHeight), + osg::Vec2f(race.mData.mFemaleWeight, race.mData.mFemaleHeight)); data->reset_data(id, scaleStats, race.mData.mFlags & ESM::Race::Beast); diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index 21298d33df..e516325455 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -41,22 +41,22 @@ namespace CSMWorld /// Tracks unique strings using RefIdSet = std::unordered_set; - class BodyAttributes + class HeightsWeights { public: - std::pair mMaleAttributes; - std::pair mFemaleAttributes; + osg::Vec2f mMaleHeightWeight; + osg::Vec2f mFemaleHeightWeight; - BodyAttributes() - : mMaleAttributes(std::make_pair(1.0f, 1.0f)) - , mFemaleAttributes(std::make_pair(1.0f, 1.0f)) + HeightsWeights() + : mMaleHeightWeight(osg::Vec2f(1.0f, 1.0f)) + , mFemaleHeightWeight(osg::Vec2f(1.0f, 1.0f)) { } - BodyAttributes(float maleHeight, float maleWeight, float femaleHeight, float femaleWeight) + HeightsWeights(const osg::Vec2f& maleHeightWeight, const osg::Vec2f& femaleHeightWeight) { - mMaleAttributes = std::make_pair(maleHeight, maleWeight); - mFemaleAttributes = std::make_pair(femaleHeight, femaleWeight); + mMaleHeightWeight = maleHeightWeight; + mFemaleHeightWeight = femaleHeightWeight; } }; @@ -77,7 +77,7 @@ namespace CSMWorld /// Retrieves the associated body part const ESM::RefId& getMalePart(ESM::PartReferenceType index) const; - const std::pair& getRaceAttributes(bool isFemale); + const osg::Vec2f& getHeightWeight(bool isFemale); /// Checks if the race has a data dependency bool hasDependency(const ESM::RefId& id) const; @@ -89,7 +89,7 @@ namespace CSMWorld void addOtherDependency(const ESM::RefId& id); /// Clears parts and dependencies void reset_data( - const ESM::RefId& raceId, const BodyAttributes& raceStats = BodyAttributes(), bool isBeast = false); + const ESM::RefId& raceId, const HeightsWeights& raceStats = HeightsWeights(), bool isBeast = false); private: bool handles(ESM::PartReferenceType type) const; @@ -97,7 +97,7 @@ namespace CSMWorld bool mIsBeast; RacePartList mFemaleParts; RacePartList mMaleParts; - BodyAttributes mHeightsWeights; + HeightsWeights mHeightsWeights; RefIdSet mDependencies; }; using RaceDataPtr = std::shared_ptr; @@ -120,7 +120,7 @@ namespace CSMWorld /// Retrieves the associated actor part ESM::RefId getPart(ESM::PartReferenceType index) const; - const std::pair& getRaceHeightWeight() const; + const osg::Vec2f& getRaceHeightWeight() const; /// Checks if the actor has a data dependency bool hasDependency(const ESM::RefId& id) const; diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index abe7b910de..e609efa5a1 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -63,9 +63,9 @@ namespace CSVRender // Attach parts to skeleton loadBodyParts(); - const std::pair attributes = mActorData.get()->getRaceHeightWeight(); + const osg::Vec2f& attributes = mActorData.get()->getRaceHeightWeight(); - mBaseNode->setScale(osg::Vec3d(attributes.second, attributes.second, attributes.first)); + mBaseNode->setScale(osg::Vec3d(attributes.x(), attributes.x(), attributes.y())); } else { From a0ba0d781a0bf7427ada397fd89de471c3483098 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 15 Feb 2024 01:50:49 +0300 Subject: [PATCH 069/451] Hide magnitude for Fortify Maximum Magicka when requested as well (#7832) --- CHANGELOG.md | 1 + apps/openmw/mwgui/widgets.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a96eb36c9f..ca925516f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -140,6 +140,7 @@ Bug #7780: Non-ASCII texture paths in NIF files don't work Bug #7785: OpenMW-CS initialising Skill and Attribute fields to 0 instead of -1 on non-FortifyStat spells Bug #7796: Absorbed enchantments don't restore magicka + Bug #7832: Ingredient tooltips show magnitude for Fortify Maximum Magicka effect Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index d824682308..fea6d490c5 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -371,7 +371,7 @@ namespace MWGui::Widgets std::string spellLine = MWMechanics::getMagicEffectString(*magicEffect, attribute, skill); - if (mEffectParams.mMagnMin || mEffectParams.mMagnMax) + if ((mEffectParams.mMagnMin || mEffectParams.mMagnMax) && !mEffectParams.mNoMagnitude) { ESM::MagicEffect::MagnitudeDisplayType displayType = magicEffect->getMagnitudeDisplayType(); if (displayType == ESM::MagicEffect::MDT_TimesInt) @@ -386,7 +386,7 @@ namespace MWGui::Widgets spellLine += formatter.str(); } - else if (displayType != ESM::MagicEffect::MDT_None && !mEffectParams.mNoMagnitude) + else if (displayType != ESM::MagicEffect::MDT_None) { spellLine += " " + MyGUI::utility::toString(mEffectParams.mMagnMin); if (mEffectParams.mMagnMin != mEffectParams.mMagnMax) From 1b1f0c4971a792622bff7f323b9642ceb0d17b4a Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 14 Feb 2024 16:41:24 -0600 Subject: [PATCH 070/451] Switch height/weight in names and make the stats a simple struct instead --- apps/opencs/model/world/actoradapter.cpp | 16 ++++++------- apps/opencs/model/world/actoradapter.hpp | 29 +++++++----------------- apps/opencs/view/render/actor.cpp | 3 +-- 3 files changed, 17 insertions(+), 31 deletions(-) diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 40bfe17989..acbe6b5d38 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -67,9 +67,9 @@ namespace CSMWorld return mMaleParts[ESM::getMeshPart(index)]; } - const osg::Vec2f& ActorAdapter::RaceData::getHeightWeight(bool isFemale) + const osg::Vec2f& ActorAdapter::RaceData::getGenderWeightHeight(bool isFemale) { - return isFemale ? mHeightsWeights.mFemaleHeightWeight : mHeightsWeights.mMaleHeightWeight; + return isFemale ? mWeightsHeights.mFemaleWeightHeight : mWeightsHeights.mMaleWeightHeight; } bool ActorAdapter::RaceData::hasDependency(const ESM::RefId& id) const @@ -95,11 +95,11 @@ namespace CSMWorld mDependencies.emplace(id); } - void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, const HeightsWeights& raceStats, bool isBeast) + void ActorAdapter::RaceData::reset_data(const ESM::RefId& id, const WeightsHeights& raceStats, bool isBeast) { mId = id; mIsBeast = isBeast; - mHeightsWeights = raceStats; + mWeightsHeights = raceStats; for (auto& str : mFemaleParts) str = ESM::RefId(); for (auto& str : mMaleParts) @@ -169,9 +169,9 @@ namespace CSMWorld return it->second.first; } - const osg::Vec2f& ActorAdapter::ActorData::getRaceHeightWeight() const + const osg::Vec2f& ActorAdapter::ActorData::getRaceWeightHeight() const { - return mRaceData->getHeightWeight(isFemale()); + return mRaceData->getGenderWeightHeight(isFemale()); } bool ActorAdapter::ActorData::hasDependency(const ESM::RefId& id) const @@ -516,8 +516,8 @@ namespace CSMWorld auto& race = raceRecord.get(); - HeightsWeights scaleStats = HeightsWeights(osg::Vec2f(race.mData.mMaleWeight, race.mData.mMaleHeight), - osg::Vec2f(race.mData.mFemaleWeight, race.mData.mFemaleHeight)); + WeightsHeights scaleStats = { osg::Vec2f(race.mData.mMaleWeight, race.mData.mMaleHeight), + osg::Vec2f(race.mData.mFemaleWeight, race.mData.mFemaleHeight) }; data->reset_data(id, scaleStats, race.mData.mFlags & ESM::Race::Beast); diff --git a/apps/opencs/model/world/actoradapter.hpp b/apps/opencs/model/world/actoradapter.hpp index e516325455..1650fc9006 100644 --- a/apps/opencs/model/world/actoradapter.hpp +++ b/apps/opencs/model/world/actoradapter.hpp @@ -41,23 +41,10 @@ namespace CSMWorld /// Tracks unique strings using RefIdSet = std::unordered_set; - class HeightsWeights + struct WeightsHeights { - public: - osg::Vec2f mMaleHeightWeight; - osg::Vec2f mFemaleHeightWeight; - - HeightsWeights() - : mMaleHeightWeight(osg::Vec2f(1.0f, 1.0f)) - , mFemaleHeightWeight(osg::Vec2f(1.0f, 1.0f)) - { - } - - HeightsWeights(const osg::Vec2f& maleHeightWeight, const osg::Vec2f& femaleHeightWeight) - { - mMaleHeightWeight = maleHeightWeight; - mFemaleHeightWeight = femaleHeightWeight; - } + osg::Vec2f mMaleWeightHeight; + osg::Vec2f mFemaleWeightHeight; }; /// Contains base race data shared between actors @@ -77,7 +64,7 @@ namespace CSMWorld /// Retrieves the associated body part const ESM::RefId& getMalePart(ESM::PartReferenceType index) const; - const osg::Vec2f& getHeightWeight(bool isFemale); + const osg::Vec2f& getGenderWeightHeight(bool isFemale); /// Checks if the race has a data dependency bool hasDependency(const ESM::RefId& id) const; @@ -88,8 +75,8 @@ namespace CSMWorld /// Marks an additional dependency void addOtherDependency(const ESM::RefId& id); /// Clears parts and dependencies - void reset_data( - const ESM::RefId& raceId, const HeightsWeights& raceStats = HeightsWeights(), bool isBeast = false); + void reset_data(const ESM::RefId& raceId, + const WeightsHeights& raceStats = { osg::Vec2f(1.f, 1.f), osg::Vec2f(1.f, 1.f) }, bool isBeast = false); private: bool handles(ESM::PartReferenceType type) const; @@ -97,7 +84,7 @@ namespace CSMWorld bool mIsBeast; RacePartList mFemaleParts; RacePartList mMaleParts; - HeightsWeights mHeightsWeights; + WeightsHeights mWeightsHeights; RefIdSet mDependencies; }; using RaceDataPtr = std::shared_ptr; @@ -120,7 +107,7 @@ namespace CSMWorld /// Retrieves the associated actor part ESM::RefId getPart(ESM::PartReferenceType index) const; - const osg::Vec2f& getRaceHeightWeight() const; + const osg::Vec2f& getRaceWeightHeight() const; /// Checks if the actor has a data dependency bool hasDependency(const ESM::RefId& id) const; diff --git a/apps/opencs/view/render/actor.cpp b/apps/opencs/view/render/actor.cpp index e609efa5a1..8aa08e443e 100644 --- a/apps/opencs/view/render/actor.cpp +++ b/apps/opencs/view/render/actor.cpp @@ -21,7 +21,6 @@ #include #include -#include "../../model/world/columns.hpp" #include "../../model/world/data.hpp" namespace CSVRender @@ -63,7 +62,7 @@ namespace CSVRender // Attach parts to skeleton loadBodyParts(); - const osg::Vec2f& attributes = mActorData.get()->getRaceHeightWeight(); + const osg::Vec2f& attributes = mActorData->getRaceWeightHeight(); mBaseNode->setScale(osg::Vec3d(attributes.x(), attributes.x(), attributes.y())); } From 54e90b4ac2693eaeaebe5caeed96728286204d98 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 14 Feb 2024 20:00:22 -0600 Subject: [PATCH 071/451] Legacy(columnimp): Add TESCS limits for race weight/height scaling --- apps/opencs/model/world/columnimp.hpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index eba09bd8b1..099dd25983 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -585,19 +585,22 @@ namespace CSMWorld void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); + + float bodyAttr = std::max(0.5f, std::min(2.0f, data.toFloat())); + if (mWeight) { if (mMale) - record2.mData.mMaleWeight = data.toFloat(); + record2.mData.mMaleWeight = bodyAttr; else - record2.mData.mFemaleWeight = data.toFloat(); + record2.mData.mFemaleWeight = bodyAttr; } else { if (mMale) - record2.mData.mMaleHeight = data.toFloat(); + record2.mData.mMaleHeight = bodyAttr; else - record2.mData.mFemaleHeight = data.toFloat(); + record2.mData.mFemaleHeight = bodyAttr; } record.setModified(record2); } From 9a7b9572fdf9b4acbba46ac4ea5cc0c27bc919aa Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 5 Feb 2024 19:13:32 +0400 Subject: [PATCH 072/451] Deploy base Qt translations when needed --- CMakeLists.txt | 39 ++++++++++++++++++++++++++---- apps/launcher/main.cpp | 13 ++-------- apps/wizard/main.cpp | 13 ++-------- components/CMakeLists.txt | 5 ++++ components/l10n/qttranslations.cpp | 37 ++++++++++++++++++++++++++++ components/l10n/qttranslations.hpp | 16 ++++++++++++ 6 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 components/l10n/qttranslations.cpp create mode 100644 components/l10n/qttranslations.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 313cc46cbe..3f5745d4a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,14 @@ if(OPENMW_GL4ES_MANUAL_INIT) add_definitions(-DOPENMW_GL4ES_MANUAL_INIT) endif() +if (APPLE OR WIN32) + set(DEPLOY_QT_TRANSLATIONS_DEFAULT ON) +else () + set(DEPLOY_QT_TRANSLATIONS_DEFAULT OFF) +endif () + +option(DEPLOY_QT_TRANSLATIONS "Deploy standard Qt translations to resources folder. Needed when OpenMW applications are deployed with Qt libraries" ${DEPLOY_QT_TRANSLATIONS_DEFAULT}) + # Apps and tools option(BUILD_OPENMW "Build OpenMW" ON) option(BUILD_LAUNCHER "Build Launcher" ON) @@ -1099,13 +1107,13 @@ if (USE_QT) if (BUILD_LAUNCHER OR BUILD_WIZARD) if (APPLE) - set(QT_TRANSLATIONS_PATH "${APP_BUNDLE_DIR}/Contents/Resources/resources/translations") + set(QT_OPENMW_TRANSLATIONS_PATH "${APP_BUNDLE_DIR}/Contents/Resources/resources/translations") else () get_generator_is_multi_config(multi_config) if (multi_config) - set(QT_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/$/resources/translations") + set(QT_OPENMW_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/$/resources/translations") else () - set(QT_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/resources/translations") + set(QT_OPENMW_TRANSLATIONS_PATH "${OpenMW_BINARY_DIR}/resources/translations") endif () endif () @@ -1121,9 +1129,30 @@ if (USE_QT) qt_add_translation(QM_FILES ${TS_FILES} OPTIONS -silent) + if (DEPLOY_QT_TRANSLATIONS) + # Once we set a Qt 6.2.0 as a minimum required version, we may use "qtpaths --qt-query" instead. + get_target_property(QT_QMAKE_EXECUTABLE Qt::qmake IMPORTED_LOCATION) + execute_process(COMMAND "${QT_QMAKE_EXECUTABLE}" -query QT_INSTALL_TRANSLATIONS + OUTPUT_VARIABLE QT_TRANSLATIONS_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) + + foreach(QM_FILE ${QM_FILES}) + get_filename_component(QM_BASENAME ${QM_FILE} NAME) + string(REGEX REPLACE "[^_]+_(.*)\\.qm" "\\1" LANG_NAME ${QM_BASENAME}) + if (EXISTS "${QT_TRANSLATIONS_DIR}/qtbase_${LANG_NAME}.qm") + set(QM_FILES ${QM_FILES} "${QT_TRANSLATIONS_DIR}/qtbase_${LANG_NAME}.qm") + elseif (EXISTS "${QT_TRANSLATIONS_DIR}/qt_${LANG_NAME}.qm") + set(QM_FILES ${QM_FILES} "${QT_TRANSLATIONS_DIR}/qt_${LANG_NAME}.qm") + else () + message(FATAL_ERROR "Qt translations for '${LANG_NAME}' locale are not found in the '${QT_TRANSLATIONS_DIR}' folder.") + endif () + endforeach(QM_FILE) + + list(REMOVE_DUPLICATES QM_FILES) + endif () + add_custom_target(qm-files - COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_TRANSLATIONS_PATH} - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QM_FILES} ${QT_TRANSLATIONS_PATH} + COMMAND ${CMAKE_COMMAND} -E make_directory ${QT_OPENMW_TRANSLATIONS_PATH} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QM_FILES} ${QT_OPENMW_TRANSLATIONS_PATH} DEPENDS ${QM_FILES} COMMENT "Copy *.qm files to resources folder") endif () diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 3cbd1f5092..7fdcac77ab 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include @@ -9,6 +8,7 @@ #include #include #include +#include #include #ifdef MAC_OS_X_VERSION_MIN_REQUIRED @@ -41,16 +41,7 @@ int runLauncher(int argc, char* argv[]) resourcesPath = Files::pathToQString(variables["resources"].as().u8string()); } - // Internationalization - QString locale = QLocale::system().name().section('_', 0, 0); - - QTranslator appTranslator; - appTranslator.load(resourcesPath + "/translations/launcher_" + locale + ".qm"); - app.installTranslator(&appTranslator); - - QTranslator componentsAppTranslator; - componentsAppTranslator.load(resourcesPath + "/translations/components_" + locale + ".qm"); - app.installTranslator(&componentsAppTranslator); + l10n::installQtTranslations(app, "launcher", resourcesPath); Launcher::MainDialog mainWin(configurationManager); diff --git a/apps/wizard/main.cpp b/apps/wizard/main.cpp index a911ac7350..b62428f946 100644 --- a/apps/wizard/main.cpp +++ b/apps/wizard/main.cpp @@ -1,11 +1,11 @@ #include #include -#include #include #include #include +#include #include "mainwizard.hpp" @@ -45,16 +45,7 @@ int main(int argc, char* argv[]) resourcesPath = Files::pathToQString(variables["resources"].as().u8string()); } - // Internationalization - QString locale = QLocale::system().name().section('_', 0, 0); - - QTranslator appTranslator; - appTranslator.load(resourcesPath + "/translations/wizard_" + locale + ".qm"); - app.installTranslator(&appTranslator); - - QTranslator componentsAppTranslator; - componentsAppTranslator.load(resourcesPath + "/translations/components_" + locale + ".qm"); - app.installTranslator(&componentsAppTranslator); + l10n::installQtTranslations(app, "wizard", resourcesPath); Wizard::MainWizard wizard; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index bb6fd1dd37..317786e96a 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -500,11 +500,16 @@ if (USE_QT) model/loadordererror view/combobox view/contentselector ) + add_component_qt_dir (config gamesettings launchersettings ) + add_component_qt_dir (l10n + qttranslations + ) + add_component_qt_dir (process processinvoker ) diff --git a/components/l10n/qttranslations.cpp b/components/l10n/qttranslations.cpp new file mode 100644 index 0000000000..9bc146699e --- /dev/null +++ b/components/l10n/qttranslations.cpp @@ -0,0 +1,37 @@ +#include "qttranslations.hpp" + +#include +#include + +namespace l10n +{ + QTranslator AppTranslator{}; + QTranslator ComponentsTranslator{}; + QTranslator QtBaseAppTranslator{}; + + void installQtTranslations(QApplication& app, const QString& appName, const QString& resourcesPath) + { + // Try to load OpenMW translations from resources folder first. + // If we loaded them, try to load Qt translations from both + // resources folder and default translations folder as well. +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + auto qtPath = QLibraryInfo::path(QLibraryInfo::TranslationsPath); +#else + auto qtPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath); +#endif + auto localPath = resourcesPath + "/translations"; + + if (AppTranslator.load(QLocale::system(), appName, "_", localPath) + && ComponentsTranslator.load(QLocale::system(), "components", "_", localPath)) + { + app.installTranslator(&AppTranslator); + app.installTranslator(&ComponentsTranslator); + + if (QtBaseAppTranslator.load(QLocale::system(), "qtbase", "_", localPath) + || QtBaseAppTranslator.load(QLocale::system(), "qt", "_", localPath) + || QtBaseAppTranslator.load(QLocale::system(), "qtbase", "_", qtPath) + || QtBaseAppTranslator.load(QLocale::system(), "qt", "_", qtPath)) + app.installTranslator(&QtBaseAppTranslator); + } + } +} diff --git a/components/l10n/qttranslations.hpp b/components/l10n/qttranslations.hpp new file mode 100644 index 0000000000..3ce87f0837 --- /dev/null +++ b/components/l10n/qttranslations.hpp @@ -0,0 +1,16 @@ +#ifndef COMPONENTS_L10N_QTTRANSLATIONS_H +#define COMPONENTS_L10N_QTTRANSLATIONS_H + +#include +#include + +namespace l10n +{ + extern QTranslator AppTranslator; + extern QTranslator ComponentsTranslator; + extern QTranslator QtBaseAppTranslator; + + void installQtTranslations(QApplication& app, const QString& appName, const QString& resourcesPath); +} + +#endif // COMPONENTS_L10N_QTTRANSLATIONS_H From 9c959d9698ff05adc493c0e8d57282d0b6d1cf94 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 16 Feb 2024 01:33:52 +0000 Subject: [PATCH 073/451] Make a long sentence more concise Thanks Bing Chat for doing a mediocre job of this that inspired me to do a competent job of it. --- docs/source/reference/modding/settings/shaders.rst | 2 +- files/settings-default.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/reference/modding/settings/shaders.rst b/docs/source/reference/modding/settings/shaders.rst index 121ecef2b1..22ceb34f44 100644 --- a/docs/source/reference/modding/settings/shaders.rst +++ b/docs/source/reference/modding/settings/shaders.rst @@ -10,7 +10,7 @@ force shaders Force rendering with shaders, even for objects that don't strictly need them. By default, only objects with certain effects, such as bump or normal maps will use shaders. -Many visual enhancements, such as :ref:`enable shadows` and :ref:`reverse z` require shaders to be used for all objects, and so behave as if this setting is true. +With enhancements enabled, such as :ref:`enable shadows` and :ref:`reverse z`, shaders must be used for all objects, as if this setting is true. Typically, one or more of these enhancements will be enabled, and shaders will be needed for everything anyway, meaning toggling this setting will have no effect. Some settings, such as :ref:`clamp lighting` only apply to objects using shaders, so enabling this option may cause slightly different visuals when used at the same time. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 4fc632f497..15f82760c5 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -407,7 +407,7 @@ console history buffer size = 4096 # Force rendering with shaders, even for objects that don't strictly need them. # By default, only objects with certain effects, such as bump or normal maps will use shaders. -# Many visual enhancements, such as "enable shadows" and "reverse z" require shaders to be used for all objects, and so behave as if this setting is true. +# With enhancements enabled, such as "enable shadows" and "reverse z", shaders must be used for all objects, as if this setting is true. # Some settings, such as "clamp lighting" only apply to objects using shaders, so enabling this option may cause slightly different visuals when used at the same time. # Otherwise, there should not be a visual difference. force shaders = false From 54f4c69d37795c9045221339b4dce602c6ff8cc7 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Thu, 15 Feb 2024 21:25:29 -0600 Subject: [PATCH 074/451] Cleanup(columnimp): Use std::clamp to limit race scaling --- apps/opencs/model/world/columnimp.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 099dd25983..0cd95b0ed2 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -586,7 +586,7 @@ namespace CSMWorld { ESXRecordT record2 = record.get(); - float bodyAttr = std::max(0.5f, std::min(2.0f, data.toFloat())); + float bodyAttr = std::clamp(data.toFloat(), 0.5f, 2.0f); if (mWeight) { From 6e81927d60d7df17195f16f8436430f173443807 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 16 Feb 2024 13:06:48 +0300 Subject: [PATCH 075/451] Make extra sure to ignore movement input for scripted animations (#7833) --- 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 cbd09b30d8..be3652b0a9 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2070,7 +2070,7 @@ namespace MWMechanics vec.x() *= speed; vec.y() *= speed; - if (isKnockedOut() || isKnockedDown() || isRecovery()) + if (isKnockedOut() || isKnockedDown() || isRecovery() || isScriptedAnimPlaying()) vec = osg::Vec3f(); CharacterState movestate = CharState_None; From 78737141034169ee844b7e67b91c77ab48f30400 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 16 Feb 2024 13:34:41 +0300 Subject: [PATCH 076/451] Restore vertical movement reset for various movement states (#7833) Note getJump already handles incapacitation states (dead/paralyzed/KO) --- apps/openmw/mwmechanics/character.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index be3652b0a9..49221cbae9 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2144,6 +2144,10 @@ namespace MWMechanics bool wasInJump = mInJump; mInJump = false; + const float jumpHeight = cls.getJump(mPtr); + if (jumpHeight <= 0.f || sneak || inwater || flying || !solid) + vec.z() = 0.f; + if (!inwater && !flying && solid) { // In the air (either getting up —ascending part of jump— or falling). @@ -2162,20 +2166,16 @@ namespace MWMechanics vec.z() = 0.0f; } // Started a jump. - else if (mJumpState != JumpState_InAir && vec.z() > 0.f && !sneak) + else if (mJumpState != JumpState_InAir && vec.z() > 0.f) { - float z = cls.getJump(mPtr); - if (z > 0.f) + mInJump = true; + if (vec.x() == 0 && vec.y() == 0) + vec.z() = jumpHeight; + else { - mInJump = true; - if (vec.x() == 0 && vec.y() == 0) - vec.z() = z; - else - { - osg::Vec3f lat(vec.x(), vec.y(), 0.0f); - lat.normalize(); - vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * z * 0.707f; - } + osg::Vec3f lat(vec.x(), vec.y(), 0.0f); + lat.normalize(); + vec = osg::Vec3f(lat.x(), lat.y(), 1.0f) * jumpHeight * 0.707f; } } } From aae74224e8cebb590e9f31b99225453d0f10934a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 16 Feb 2024 14:28:43 +0300 Subject: [PATCH 077/451] Prevent swim upward correction from causing false-positive jumping events (#7833) --- apps/openmw/mwmechanics/character.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 49221cbae9..d1f41ad514 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2146,7 +2146,12 @@ namespace MWMechanics mInJump = false; const float jumpHeight = cls.getJump(mPtr); if (jumpHeight <= 0.f || sneak || inwater || flying || !solid) + { vec.z() = 0.f; + // Following code might assign some vertical movement regardless, need to reset this manually + // This is used for jumping detection + movementSettings.mPosition[2] = 0; + } if (!inwater && !flying && solid) { From d9ee54ae98a2a75cd37fd980ebd16dabe5e2f251 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 16 Feb 2024 11:33:57 +0300 Subject: [PATCH 078/451] DebugCustomDraw: Correct PerContextProgram use, clean up drawImplementation --- components/debug/debugdraw.cpp | 56 +++++++++++++--------------------- 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index bbbf2c238e..e7aa7d8cce 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -265,59 +265,45 @@ namespace Debug const osg::StateSet* stateSet = getStateSet(); - auto program = static_cast(stateSet->getAttribute(osg::StateAttribute::PROGRAM)); - const osg::Program::PerContextProgram* pcp = program->getPCP(*state); - if (!pcp) - { - return; - } - - const osg::Uniform* uTrans = stateSet->getUniform("trans"); - const osg::Uniform* uCol = stateSet->getUniform("color"); - const osg::Uniform* uScale = stateSet->getUniform("scale"); - const osg::Uniform* uUseNormalAsColor = stateSet->getUniform("useNormalAsColor"); - - auto transLocation = pcp->getUniformLocation(uTrans->getNameID()); - auto colLocation = pcp->getUniformLocation(uCol->getNameID()); - auto scaleLocation = pcp->getUniformLocation(uScale->getNameID()); - auto normalAsColorLocation = pcp->getUniformLocation(uUseNormalAsColor->getNameID()); - - ext->glUniform3f(transLocation, 0., 0., 0.); - ext->glUniform3f(colLocation, 1., 1., 1.); - ext->glUniform3f(scaleLocation, 1., 1., 1.); - ext->glUniform1i(normalAsColorLocation, true); - - mLinesToDraw->drawImplementation(renderInfo); + const osg::Program::PerContextProgram& pcp = *state->getLastAppliedProgramObject(); + auto transLocation = pcp.getUniformLocation(stateSet->getUniform("trans")->getNameID()); + auto colLocation = pcp.getUniformLocation(stateSet->getUniform("color")->getNameID()); + auto scaleLocation = pcp.getUniformLocation(stateSet->getUniform("scale")->getNameID()); + auto normalAsColorLocation = pcp.getUniformLocation(stateSet->getUniform("useNormalAsColor")->getNameID()); + + auto drawPrimitive = [&](const osg::Drawable* primitive, const osg::Vec3f& pos, const osg::Vec3f& color, + const osg::Vec3f& scale, const bool normalAsColor) { + ext->glUniform3f(transLocation, pos.x(), pos.y(), pos.z()); + ext->glUniform3f(colLocation, color.x(), color.y(), color.z()); + ext->glUniform3f(scaleLocation, scale.x(), scale.y(), scale.z()); + ext->glUniform1i(normalAsColorLocation, normalAsColor); + primitive->drawImplementation(renderInfo); + }; - ext->glUniform1i(normalAsColorLocation, false); + drawPrimitive(mLinesToDraw, { 0.f, 0.f, 0.f }, { 1.f, 1.f, 1.f }, { 1.f, 1.f, 1.f }, true); for (const auto& shapeToDraw : mShapesToDraw) { - osg::Vec3f translation = shapeToDraw.mPosition; - osg::Vec3f color = shapeToDraw.mColor; - osg::Vec3f scale = shapeToDraw.mDims; - - ext->glUniform3f(transLocation, translation.x(), translation.y(), translation.z()); - ext->glUniform3f(colLocation, color.x(), color.y(), color.z()); - ext->glUniform3f(scaleLocation, scale.x(), scale.y(), scale.z()); - + const osg::Geometry* geometry = nullptr; switch (shapeToDraw.mDrawShape) { case DrawShape::Cube: - mCubeGeometry->drawImplementation(renderInfo); + geometry = mCubeGeometry; break; case DrawShape::Cylinder: - mCylinderGeometry->drawImplementation(renderInfo); + geometry = mCylinderGeometry; break; case DrawShape::WireCube: - mWireCubeGeometry->drawImplementation(renderInfo); + geometry = mWireCubeGeometry; break; } + drawPrimitive(geometry, shapeToDraw.mPosition, shapeToDraw.mColor, shapeToDraw.mDims, false); } mShapesToDraw.clear(); static_cast(mLinesToDraw->getVertexArray())->clear(); static_cast(mLinesToDraw->getNormalArray())->clear(); static_cast(mLinesToDraw->getPrimitiveSet(0))->setCount(0); + pcp.resetAppliedUniforms(); } } From d147d1d250d7ec19cc59a4c25d1d946eab9e6bc9 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 30 Jan 2024 01:19:42 +0100 Subject: [PATCH 079/451] Initialize FileSystemArchive index in constructor It should be initialize for each created archive anyway. There is no good reason to have additional complexity for lazy initialization. And it helps to catch problems with specific directory when it's added to the VFS not when all are added and index is built. --- components/vfs/filesystemarchive.cpp | 62 +++++++++++----------------- components/vfs/filesystemarchive.hpp | 1 - 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index c72798e7ea..b8374eaba9 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -12,50 +12,38 @@ namespace VFS { FileSystemArchive::FileSystemArchive(const std::filesystem::path& path) - : mBuiltIndex(false) - , mPath(path) - { - } - - void FileSystemArchive::listResources(FileMap& out) + : mPath(path) { - if (!mBuiltIndex) - { - const auto str = mPath.u8string(); - size_t prefix = str.size(); - - if (!mPath.empty() && str[prefix - 1] != '\\' && str[prefix - 1] != '/') - ++prefix; - - for (const auto& i : std::filesystem::recursive_directory_iterator(mPath)) - { - if (std::filesystem::is_directory(i)) - continue; + const auto str = mPath.u8string(); + std::size_t prefix = str.size(); - const auto& path = i.path(); - const std::string proper = Files::pathToUnicodeString(path); + if (prefix > 0 && str[prefix - 1] != '\\' && str[prefix - 1] != '/') + ++prefix; - FileSystemArchiveFile file(path); - - VFS::Path::Normalized searchable(std::string_view{ proper }.substr(prefix)); - - const auto inserted = mIndex.emplace(std::move(searchable), std::move(file)); - if (!inserted.second) - Log(Debug::Warning) - << "Warning: found duplicate file for '" << proper - << "', please check your file system for two files with the same name in different cases."; - else - out[inserted.first->first] = &inserted.first->second; - } - mBuiltIndex = true; - } - else + for (const auto& i : std::filesystem::recursive_directory_iterator(mPath)) { - for (auto& [k, v] : mIndex) - out[k] = &v; + if (std::filesystem::is_directory(i)) + continue; + + const std::filesystem::path& filePath = i.path(); + const std::string proper = Files::pathToUnicodeString(filePath); + VFS::Path::Normalized searchable(std::string_view{ proper }.substr(prefix)); + FileSystemArchiveFile file(filePath); + + const auto inserted = mIndex.emplace(std::move(searchable), std::move(file)); + if (!inserted.second) + Log(Debug::Warning) + << "Found duplicate file for '" << proper + << "', please check your file system for two files with the same name in different cases."; } } + void FileSystemArchive::listResources(FileMap& out) + { + for (auto& [k, v] : mIndex) + out[k] = &v; + } + bool FileSystemArchive::contains(Path::NormalizedView file) const { return mIndex.find(file) != mIndex.end(); diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index b158ef3472..215c443b58 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -36,7 +36,6 @@ namespace VFS private: std::map> mIndex; - bool mBuiltIndex; std::filesystem::path mPath; }; From 95ade48292eb922579334c20d2d7bdafd755fad7 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 16 Feb 2024 19:22:34 -0600 Subject: [PATCH 080/451] Fix menu doc --- files/lua_api/openmw/core.lua | 7 +++++++ files/lua_api/openmw/types.lua | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 330f0e20a0..169a41b2d2 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -24,6 +24,13 @@ -- @param #string eventName -- @param eventData +--- +-- Send an event to menu scripts. +-- @function [parent=#core] sendMenuEvent +-- @param openmw.core#GameObject player +-- @param #string eventName +-- @param eventData + --- -- Simulation time in seconds. -- The number of simulation seconds passed in the game world since starting a new game. diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index d16f560a58..56deb6d558 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1097,13 +1097,6 @@ -- @field #string texture Birth sign texture -- @field #list<#string> spells A read-only list containing the ids of all spells gained from this sign. ---- --- Send an event to menu scripts. --- @function [parent=#core] sendMenuEvent --- @param openmw.core#GameObject player --- @param #string eventName --- @param eventData - -------------------------------------------------------------------------------- -- @{#Armor} functions -- @field [parent=#types] #Armor Armor From a2345194c87170ca215f7eac9b83ac86bf4f5447 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 30 Jan 2024 00:46:38 +0100 Subject: [PATCH 081/451] Optimize lookup for a file in the BSA archive Use binary search in sorted vector or normalized paths instead of linear search in the original file struct. With number of files from 1k to 10k in vanilla archives this gives some benefits. --- components/vfs/bsaarchive.hpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 847aeca509..2276933684 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -10,6 +10,8 @@ #include #include +#include + namespace VFS { template @@ -44,7 +46,10 @@ namespace VFS for (Bsa::BSAFile::FileList::const_iterator it = filelist.begin(); it != filelist.end(); ++it) { mResources.emplace_back(&*it, mFile.get()); + mFiles.emplace_back(it->name()); } + + std::sort(mFiles.begin(), mFiles.end()); } virtual ~BsaArchive() {} @@ -57,12 +62,7 @@ namespace VFS bool contains(Path::NormalizedView file) const override { - for (const auto& it : mResources) - { - if (Path::pathEqual(file.value(), it.mInfo->name())) - return true; - } - return false; + return std::binary_search(mFiles.begin(), mFiles.end(), file); } std::string getDescription() const override { return std::string{ "BSA: " } + mFile->getFilename(); } @@ -70,6 +70,7 @@ namespace VFS private: std::unique_ptr mFile; std::vector> mResources; + std::vector mFiles; }; template From 1b1ed55762c4edab2335c3ca9bc46db35cc0d125 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 17 Feb 2024 14:10:45 +0100 Subject: [PATCH 082/451] Add context to the errors on recursive iteration over directory To avoid showing users errors like: recursive_directory_iterator::operator++: Access is denied. And show something like this: Failed to recursively iterate over "/home/elsid/.local/share/openmw/test_data" when incrementing to the next item from "/home/elsid/.local/share/openmw/test_data/permission_denied": Permission denied --- components/vfs/filesystemarchive.cpp | 43 +++++++++++++++++++--------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index b8374eaba9..01e5c2a1b5 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -20,21 +20,36 @@ namespace VFS if (prefix > 0 && str[prefix - 1] != '\\' && str[prefix - 1] != '/') ++prefix; - for (const auto& i : std::filesystem::recursive_directory_iterator(mPath)) + std::filesystem::recursive_directory_iterator iterator(mPath); + + for (auto it = std::filesystem::begin(iterator), end = std::filesystem::end(iterator); it != end;) { - if (std::filesystem::is_directory(i)) - continue; - - const std::filesystem::path& filePath = i.path(); - const std::string proper = Files::pathToUnicodeString(filePath); - VFS::Path::Normalized searchable(std::string_view{ proper }.substr(prefix)); - FileSystemArchiveFile file(filePath); - - const auto inserted = mIndex.emplace(std::move(searchable), std::move(file)); - if (!inserted.second) - Log(Debug::Warning) - << "Found duplicate file for '" << proper - << "', please check your file system for two files with the same name in different cases."; + const auto& i = *it; + + if (!std::filesystem::is_directory(i)) + { + const std::filesystem::path& filePath = i.path(); + const std::string proper = Files::pathToUnicodeString(filePath); + VFS::Path::Normalized searchable(std::string_view{ proper }.substr(prefix)); + FileSystemArchiveFile file(filePath); + + const auto inserted = mIndex.emplace(std::move(searchable), std::move(file)); + if (!inserted.second) + Log(Debug::Warning) + << "Found duplicate file for '" << proper + << "', please check your file system for two files with the same name in different cases."; + } + + // Exception thrown by the operator++ may not contain the context of the error like what exact path caused + // the problem which makes it hard to understand what's going on when iteration happens over a directory + // with thousands of files and subdirectories. + const std::filesystem::path prevPath = i.path(); + std::error_code ec; + it.increment(ec); + if (ec != std::error_code()) + throw std::runtime_error("Failed to recursively iterate over \"" + Files::pathToUnicodeString(mPath) + + "\" when incrementing to the next item from \"" + Files::pathToUnicodeString(prevPath) + + "\": " + ec.message()); } } From 41d41780a836b5e7f7ec4ffd052c86c945d17f8c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 18 Feb 2024 13:16:47 +0300 Subject: [PATCH 083/451] CharacterController: rework movement queueing logic (#7835) --- apps/openmw/mwmechanics/character.cpp | 86 +++++++++++++++------------ 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index cbd09b30d8..d89d4d8d53 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2237,8 +2237,6 @@ namespace MWMechanics if (mAnimation->isPlaying(mCurrentJump)) jumpstate = JumpState_Landing; - vec.x() *= scale; - vec.y() *= scale; vec.z() = 0.0f; if (movementSettings.mIsStrafing) @@ -2371,7 +2369,8 @@ namespace MWMechanics const float speedMult = speed / mMovementAnimSpeed; mAnimation->adjustSpeedMult(mCurrentMovement, std::min(maxSpeedMult, speedMult)); // Make sure the actual speed is the "expected" speed even though the animation is slower - scale *= std::max(1.f, speedMult / maxSpeedMult); + if (isMovementAnimationControlled()) + scale *= std::max(1.f, speedMult / maxSpeedMult); } if (!mSkipAnim) @@ -2390,20 +2389,17 @@ namespace MWMechanics } } - if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) - world->queueMovement(mPtr, vec); + updateHeadTracking(duration); } movement = vec; movementSettings.mPosition[0] = movementSettings.mPosition[1] = 0; + + // Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicsSystem will + // actually handle it in this frame due to the fixed minimum timestep used for the physics update. It will + // be reset in PhysicsSystem::move once the jump is handled. if (movement.z() == 0.f) movementSettings.mPosition[2] = 0; - // Can't reset jump state (mPosition[2]) here in full; we don't know for sure whether the PhysicSystem will - // actually handle it in this frame due to the fixed minimum timestep used for the physics update. It will - // be reset in PhysicSystem::move once the jump is handled. - - if (!mSkipAnim) - updateHeadTracking(duration); } else if (cls.getCreatureStats(mPtr).isDead()) { @@ -2420,35 +2416,42 @@ namespace MWMechanics osg::Vec3f movementFromAnimation = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); - if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) + if (mPtr.getClass().isActor() && !isScriptedAnimPlaying()) { - if (duration > 0.0f) - movementFromAnimation /= duration; - else - movementFromAnimation = osg::Vec3f(0.f, 0.f, 0.f); - - movementFromAnimation.x() *= scale; - movementFromAnimation.y() *= scale; + if (isMovementAnimationControlled()) + { + if (duration != 0.f && movementFromAnimation != osg::Vec3f()) + { + movementFromAnimation /= duration; + + // Ensure we're moving in the right general direction. + // In vanilla, all horizontal movement is taken from animations, even when moving diagonally (which + // doesn't have a corresponding animation). So to achieve diagonal movement, we have to rotate the + // movement taken from the animation to the intended direction. + // + // Note that while a complete movement animation cycle will have a well defined direction, no + // individual frame will, and therefore we have to determine the direction based on the currently + // playing cycle instead. + if (speed > 0.f) + { + float animMovementAngle = getAnimationMovementDirection(); + float targetMovementAngle = std::atan2(-movement.x(), movement.y()); + float diff = targetMovementAngle - animMovementAngle; + movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; + } - if (speed > 0.f && movementFromAnimation != osg::Vec3f()) + movement = movementFromAnimation; + } + else + { + movement = osg::Vec3f(); + } + } + else if (mSkipAnim) { - // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from - // animations, even when moving diagonally (which doesn't have a corresponding animation). So to acheive - // diagonal movement, we have to rotate the movement taken from the animation to the intended - // direction. - // - // Note that while a complete movement animation cycle will have a well defined direction, no individual - // frame will, and therefore we have to determine the direction based on the currently playing cycle - // instead. - float animMovementAngle = getAnimationMovementDirection(); - float targetMovementAngle = std::atan2(-movement.x(), movement.y()); - float diff = targetMovementAngle - animMovementAngle; - movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; + movement = osg::Vec3f(); } - if (!(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) - movement = movementFromAnimation; - if (mFloatToSurface) { if (cls.getCreatureStats(mPtr).isDead() @@ -2463,8 +2466,11 @@ namespace MWMechanics } } + movement.x() *= scale; + movement.y() *= scale; // Update movement - world->queueMovement(mPtr, movement); + if (movement != osg::Vec3f()) + world->queueMovement(mPtr, movement); } mSkipAnim = false; @@ -2681,11 +2687,15 @@ namespace MWMechanics bool CharacterController::isMovementAnimationControlled() const { + if (Settings::game().mPlayerMovementIgnoresAnimation && mPtr == getPlayer()) + return false; + + if (mInJump) + return false; + bool movementAnimationControlled = mIdleState != CharState_None; if (mMovementState != CharState_None) movementAnimationControlled = mMovementAnimationHasMovement; - if (mInJump) - movementAnimationControlled = false; return movementAnimationControlled; } From 8cc665ec4354ca9163f0e07d022b170bfecda673 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 14:24:54 +0100 Subject: [PATCH 084/451] Update google benchmark to 1.8.3 --- extern/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 10d75c1057..4a2cf1162b 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -212,8 +212,8 @@ if (BUILD_BENCHMARKS AND NOT OPENMW_USE_SYSTEM_BENCHMARK) include(FetchContent) FetchContent_Declare(benchmark - URL https://github.com/google/benchmark/archive/refs/tags/v1.7.1.zip - URL_HASH SHA512=bec4016263587a57648e02b094c69e838c0a21e16c3dcfc6f03100397ab8f95d5fab1f5fd0d7e0e8adbb8212fff1eb574581158fdda1fa7fd6ff12762154b0cc + URL https://github.com/google/benchmark/archive/refs/tags/v1.8.3.zip + URL_HASH SHA512=d73587ad9c49338749e1d117a6f8c7ff9c603a91a2ffa91a7355c7df7dea82710b9a810d34ddfef20973ecdc77092ec10fb2b4e4cc8d2e7810cbed79617b3828 SOURCE_DIR fetched/benchmark ) FetchContent_MakeAvailableExcludeFromAll(benchmark) From da5ab2b2c97ec2c284d035ef480099e8304f7479 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 14:22:07 +0100 Subject: [PATCH 085/451] Fix benchmark warning: -Wdeprecated-declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /home/elsid/dev/openmw/apps/benchmarks/detournavigator/navmeshtilescache.cpp: In function ‘void {anonymous}::getFromFilledCache(benchmark::State&)’: /home/elsid/dev/openmw/apps/benchmarks/detournavigator/navmeshtilescache.cpp:186:37: warning: ‘typename std::enable_if<((! std::is_trivially_copyable<_Tp>::value) || (sizeof (Tp) > sizeof (Tp*)))>::type benchmark::DoNotOptimize(const Tp&) [with Tp = DetourNavigator::NavMeshTilesCache::Value; typename std::enable_if<((! std::is_trivially_copyable<_Tp>::value) || (sizeof (Tp) > sizeof (Tp*)))>::type = void]’ is deprecated: The const-ref version of this method can permit undesired compiler optimizations in benchmarks [-Wdeprecated-declarations] 186 | benchmark::DoNotOptimize(result); | ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~ In file included from /home/elsid/dev/openmw/apps/benchmarks/detournavigator/navmeshtilescache.cpp:1: /home/elsid/dev/benchmark/build/gcc/release/install/include/benchmark/benchmark.h:507:5: note: declared here 507 | DoNotOptimize(Tp const& value) { | ^~~~~~~~~~~~~ --- apps/benchmarks/detournavigator/navmeshtilescache.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/benchmarks/detournavigator/navmeshtilescache.cpp b/apps/benchmarks/detournavigator/navmeshtilescache.cpp index 746739c856..26873d9a03 100644 --- a/apps/benchmarks/detournavigator/navmeshtilescache.cpp +++ b/apps/benchmarks/detournavigator/navmeshtilescache.cpp @@ -182,7 +182,7 @@ namespace for (auto _ : state) { const auto& key = keys[n++ % keys.size()]; - const auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh); + auto result = cache.get(key.mAgentBounds, key.mTilePosition, key.mRecastMesh); benchmark::DoNotOptimize(result); } } @@ -241,7 +241,7 @@ namespace while (state.KeepRunning()) { const auto& key = keys[n++ % keys.size()]; - const auto result = cache.set( + auto result = cache.set( key.mAgentBounds, key.mTilePosition, key.mRecastMesh, std::make_unique()); benchmark::DoNotOptimize(result); } From df077a2524095333d89d231b735396867f2d2ff8 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 30 Jan 2024 01:13:19 +0100 Subject: [PATCH 086/451] Simplify and reduce code duplication for BSA archive creation --- apps/niftest/niftest.cpp | 43 ++++++++++------------------ components/vfs/bsaarchive.hpp | 44 ++++++++++++----------------- components/vfs/registerarchives.cpp | 13 +-------- 3 files changed, 34 insertions(+), 66 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index e18da8e3f6..fe60238cd5 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -42,29 +42,10 @@ bool isBSA(const std::filesystem::path& filename) return hasExtension(filename, ".bsa") || hasExtension(filename, ".ba2"); } -std::unique_ptr makeBsaArchive(const std::filesystem::path& path) -{ - switch (Bsa::BSAFile::detectVersion(path)) - { - case Bsa::BSAVER_COMPRESSED: - return std::make_unique::type>(path); - case Bsa::BSAVER_BA2_GNRL: - return std::make_unique::type>(path); - case Bsa::BSAVER_BA2_DX10: - return std::make_unique::type>(path); - case Bsa::BSAVER_UNCOMPRESSED: - return std::make_unique::type>(path); - case Bsa::BSAVER_UNKNOWN: - default: - std::cerr << "'" << Files::pathToUnicodeString(path) << "' is not a recognized BSA archive" << std::endl; - return nullptr; - } -} - std::unique_ptr makeArchive(const std::filesystem::path& path) { if (isBSA(path)) - return makeBsaArchive(path); + return VFS::makeBsaArchive(path); if (std::filesystem::is_directory(path)) return std::make_unique(path); return nullptr; @@ -124,17 +105,23 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat if (!archivePath.empty() && !isBSA(archivePath)) { - Files::PathContainer dataDirs = { archivePath }; - const Files::Collections fileCollections = Files::Collections(dataDirs); + const Files::Collections fileCollections({ archivePath }); const Files::MultiDirCollection& bsaCol = fileCollections.getCollection(".bsa"); const Files::MultiDirCollection& ba2Col = fileCollections.getCollection(".ba2"); - for (auto& file : bsaCol) + for (const Files::MultiDirCollection& collection : { bsaCol, ba2Col }) { - readVFS(makeBsaArchive(file.second), file.second, quiet); - } - for (auto& file : ba2Col) - { - readVFS(makeBsaArchive(file.second), file.second, quiet); + for (auto& file : collection) + { + try + { + readVFS(VFS::makeBsaArchive(file.second), file.second, quiet); + } + catch (const std::exception& e) + { + std::cerr << "Failed to read archive file '" << Files::pathToUnicodeString(file.second) + << "': " << e.what() << std::endl; + } + } } } } diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 2276933684..83a68a0589 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -11,6 +11,8 @@ #include #include +#include +#include namespace VFS { @@ -73,34 +75,24 @@ namespace VFS std::vector mFiles; }; - template - struct ArchiveSelector + inline std::unique_ptr makeBsaArchive(const std::filesystem::path& path) { - }; - - template <> - struct ArchiveSelector - { - using type = BsaArchive; - }; - - template <> - struct ArchiveSelector - { - using type = BsaArchive; - }; - - template <> - struct ArchiveSelector - { - using type = BsaArchive; - }; + switch (Bsa::BSAFile::detectVersion(path)) + { + case Bsa::BSAVER_UNKNOWN: + break; + case Bsa::BSAVER_UNCOMPRESSED: + return std::make_unique>(path); + case Bsa::BSAVER_COMPRESSED: + return std::make_unique>(path); + case Bsa::BSAVER_BA2_GNRL: + return std::make_unique>(path); + case Bsa::BSAVER_BA2_DX10: + return std::make_unique>(path); + } - template <> - struct ArchiveSelector - { - using type = BsaArchive; - }; + throw std::runtime_error("Unknown archive type '" + Files::pathToUnicodeString(path) + "'"); + } } #endif diff --git a/components/vfs/registerarchives.cpp b/components/vfs/registerarchives.cpp index 9dbe878bca..f017b5f73c 100644 --- a/components/vfs/registerarchives.cpp +++ b/components/vfs/registerarchives.cpp @@ -25,18 +25,7 @@ namespace VFS // Last BSA has the highest priority const auto archivePath = collections.getPath(*archive); Log(Debug::Info) << "Adding BSA archive " << archivePath; - Bsa::BsaVersion bsaVersion = Bsa::BSAFile::detectVersion(archivePath); - - if (bsaVersion == Bsa::BSAVER_COMPRESSED) - vfs->addArchive(std::make_unique::type>(archivePath)); - else if (bsaVersion == Bsa::BSAVER_BA2_GNRL) - vfs->addArchive(std::make_unique::type>(archivePath)); - else if (bsaVersion == Bsa::BSAVER_BA2_DX10) - vfs->addArchive(std::make_unique::type>(archivePath)); - else if (bsaVersion == Bsa::BSAVER_UNCOMPRESSED) - vfs->addArchive(std::make_unique::type>(archivePath)); - else - throw std::runtime_error("Unknown archive type '" + *archive + "'"); + vfs->addArchive(makeBsaArchive(archivePath)); } else { From cc9f9b53bafaaecaf9d3bf961e64cb4c31a8987b Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 30 Jan 2024 01:29:38 +0100 Subject: [PATCH 087/451] Convert BsaVersion to enum class --- apps/bsatool/bsatool.cpp | 16 +++++++++------- apps/launcher/datafilespage.cpp | 2 +- components/bsa/bsa_file.cpp | 18 +++++++++--------- components/bsa/bsa_file.hpp | 12 ++++++------ components/bsa/compressedbsafile.cpp | 2 +- components/vfs/bsaarchive.hpp | 10 +++++----- 6 files changed, 31 insertions(+), 29 deletions(-) diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index 28711df929..171e5606c4 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -329,17 +329,19 @@ int main(int argc, char** argv) switch (bsaVersion) { - case Bsa::BSAVER_COMPRESSED: + case Bsa::BsaVersion::Unknown: + break; + case Bsa::BsaVersion::Uncompressed: + return call(info); + case Bsa::BsaVersion::Compressed: return call(info); - case Bsa::BSAVER_BA2_GNRL: + case Bsa::BsaVersion::BA2GNRL: return call(info); - case Bsa::BSAVER_BA2_DX10: + case Bsa::BsaVersion::BA2DX10: return call(info); - case Bsa::BSAVER_UNCOMPRESSED: - return call(info); - default: - throw std::runtime_error("Unrecognised BSA archive"); } + + throw std::runtime_error("Unrecognised BSA archive"); } catch (std::exception& e) { diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 9b14a91934..92b86e9cec 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -819,7 +819,7 @@ void Launcher::DataFilesPage::addArchivesFromDir(const QString& path) for (const auto& fileinfo : dir.entryInfoList(archiveFilter)) { const auto absPath = fileinfo.absoluteFilePath(); - if (Bsa::BSAFile::detectVersion(Files::pathFromQString(absPath)) == Bsa::BSAVER_UNKNOWN) + if (Bsa::BSAFile::detectVersion(Files::pathFromQString(absPath)) == Bsa::BsaVersion::Unknown) continue; const auto fileName = fileinfo.fileName(); diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index b3e24c75ab..c8b0021152 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -335,7 +335,7 @@ BsaVersion Bsa::BSAFile::detectVersion(const std::filesystem::path& filePath) if (fsize < 12) { - return BSAVER_UNKNOWN; + return BsaVersion::Unknown; } // Get essential header numbers @@ -345,23 +345,23 @@ BsaVersion Bsa::BSAFile::detectVersion(const std::filesystem::path& filePath) input.read(reinterpret_cast(head), 12); - if (head[0] == static_cast(BSAVER_UNCOMPRESSED)) + if (head[0] == static_cast(BsaVersion::Uncompressed)) { - return BSAVER_UNCOMPRESSED; + return BsaVersion::Uncompressed; } - if (head[0] == static_cast(BSAVER_COMPRESSED) || head[0] == ESM::fourCC("BTDX")) + if (head[0] == static_cast(BsaVersion::Compressed) || head[0] == ESM::fourCC("BTDX")) { if (head[1] == static_cast(0x01)) { if (head[2] == ESM::fourCC("GNRL")) - return BSAVER_BA2_GNRL; + return BsaVersion::BA2GNRL; if (head[2] == ESM::fourCC("DX10")) - return BSAVER_BA2_DX10; - return BSAVER_UNKNOWN; + return BsaVersion::BA2DX10; + return BsaVersion::Unknown; } - return BSAVER_COMPRESSED; + return BsaVersion::Compressed; } - return BSAVER_UNKNOWN; + return BsaVersion::Unknown; } diff --git a/components/bsa/bsa_file.hpp b/components/bsa/bsa_file.hpp index 8a953245d2..03a0703885 100644 --- a/components/bsa/bsa_file.hpp +++ b/components/bsa/bsa_file.hpp @@ -35,13 +35,13 @@ namespace Bsa { - enum BsaVersion + enum class BsaVersion : std::uint32_t { - BSAVER_UNKNOWN = 0x0, - BSAVER_UNCOMPRESSED = 0x100, - BSAVER_COMPRESSED = 0x415342, // B, S, A, - BSAVER_BA2_GNRL, // used by FO4, BSA which contains files - BSAVER_BA2_DX10 // used by FO4, BSA which contains textures + Unknown = 0x0, + Uncompressed = 0x100, + Compressed = 0x415342, // B, S, A, + BA2GNRL, // used by FO4, BSA which contains files + BA2DX10 // used by FO4, BSA which contains textures }; /** diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index b916de7919..99efe7a587 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -70,7 +70,7 @@ namespace Bsa input.read(reinterpret_cast(&mHeader), sizeof(mHeader)); - if (mHeader.mFormat != BSAVER_COMPRESSED) // BSA + if (mHeader.mFormat != static_cast(BsaVersion::Compressed)) // BSA fail("Unrecognized compressed BSA format"); if (mHeader.mVersion != Version_TES4 && mHeader.mVersion != Version_FO3 && mHeader.mVersion != Version_SSE) fail("Unrecognized compressed BSA version"); diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 83a68a0589..9418cce745 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -79,15 +79,15 @@ namespace VFS { switch (Bsa::BSAFile::detectVersion(path)) { - case Bsa::BSAVER_UNKNOWN: + case Bsa::BsaVersion::Unknown: break; - case Bsa::BSAVER_UNCOMPRESSED: + case Bsa::BsaVersion::Uncompressed: return std::make_unique>(path); - case Bsa::BSAVER_COMPRESSED: + case Bsa::BsaVersion::Compressed: return std::make_unique>(path); - case Bsa::BSAVER_BA2_GNRL: + case Bsa::BsaVersion::BA2GNRL: return std::make_unique>(path); - case Bsa::BSAVER_BA2_DX10: + case Bsa::BsaVersion::BA2DX10: return std::make_unique>(path); } From 8c6e0866e0194465a77560ed887edcb4c78cffb2 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 30 Jan 2024 01:47:49 +0100 Subject: [PATCH 088/451] Avoid seek for detecting BSA type Seek is pretty expensive operation. Try to read first 12 bytes instead. --- components/bsa/bsa_file.cpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/components/bsa/bsa_file.cpp b/components/bsa/bsa_file.cpp index c8b0021152..4704e6e7e0 100644 --- a/components/bsa/bsa_file.cpp +++ b/components/bsa/bsa_file.cpp @@ -325,25 +325,15 @@ BsaVersion Bsa::BSAFile::detectVersion(const std::filesystem::path& filePath) { std::ifstream input(filePath, std::ios_base::binary); - // Total archive size - std::streamoff fsize = 0; - if (input.seekg(0, std::ios_base::end)) - { - fsize = input.tellg(); - input.seekg(0); - } - - if (fsize < 12) - { - return BsaVersion::Unknown; - } - // Get essential header numbers // First 12 bytes uint32_t head[3]; - input.read(reinterpret_cast(head), 12); + input.read(reinterpret_cast(head), sizeof(head)); + + if (input.gcount() != sizeof(head)) + return BsaVersion::Unknown; if (head[0] == static_cast(BsaVersion::Uncompressed)) { From e2e1d913af97c13115a4f5ef62126264f770b173 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 14 Feb 2024 22:37:56 +0100 Subject: [PATCH 089/451] Remove redundant destructor --- components/vfs/bsaarchive.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 9418cce745..664466fa40 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -54,8 +54,6 @@ namespace VFS std::sort(mFiles.begin(), mFiles.end()); } - virtual ~BsaArchive() {} - void listResources(FileMap& out) override { for (auto& resource : mResources) From e9c672b29771433cd73e1bd560da62049e9bf38c Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 19:14:51 +0100 Subject: [PATCH 090/451] Fix build with cmake flag BUILD_SHARED_LIBS=ON Always build opencs-lib as static library instead of BUILD_SHARED_LIBS deciding whether it's static or shared library. --- .gitlab-ci.yml | 1 + CI/before_script.linux.sh | 2 +- apps/opencs/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7dc69484c6..1a8e22ed82 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -210,6 +210,7 @@ Ubuntu_GCC_Debug: CCACHE_SIZE: 3G CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -O0 + BUILD_SHARED_LIBS: 1 # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 0edd38628f..ab61ed3e59 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -22,7 +22,7 @@ declare -a CMAKE_CONF_OPTS=( -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_INSTALL_PREFIX=install - -DBUILD_SHARED_LIBS=OFF + -DBUILD_SHARED_LIBS="${BUILD_SHARED_LIBS:-OFF}" -DUSE_SYSTEM_TINYXML=ON -DOPENMW_USE_SYSTEM_RECASTNAVIGATION=ON -DOPENMW_CXX_FLAGS="-Werror -Werror=implicit-fallthrough" # flags specific to OpenMW project diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 610c5157aa..9bf02e10c9 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -172,7 +172,7 @@ else() set (OPENCS_OPENMW_CFG "") endif(APPLE) -add_library(openmw-cs-lib +add_library(openmw-cs-lib STATIC ${OPENCS_SRC} ${OPENCS_UI_HDR} ${OPENCS_MOC_SRC} From efbc37d22fec87633e39a02fc19a54908201cccb Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 19:36:53 +0100 Subject: [PATCH 091/451] Build components with position independent code only for Android openmw is build as shared library with position independent code enabled there so linked static libraries need to have this too. --- components/CMakeLists.txt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 317786e96a..dc195d8d0b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -526,11 +526,9 @@ if (USE_QT) QT_WRAP_UI(ESM_UI_HDR ${ESM_UI}) endif() -if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64" AND NOT APPLE) - add_definitions(-fPIC) - endif() -endif () +if (ANDROID) + set_property(TARGET components PROPERTY POSTION_INDEPENDENT_CODE ON) +endif() include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) From 731095831d7ab5394bf006ad213243d56510e0e4 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sun, 18 Feb 2024 13:15:56 -0600 Subject: [PATCH 092/451] Add missing function types.Item.isCarriable() --- apps/openmw/mwlua/types/item.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index d1f80e44f4..ff05c3f232 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -1,3 +1,4 @@ +#include #include #include "../../mwmechanics/spellutil.hpp" @@ -24,6 +25,15 @@ namespace MWLua item["isRestocking"] = [](const Object& object) -> bool { return object.ptr().getCellRef().getCount(false) < 0; }; + item["isCarriable"] = [](const Object& object) -> bool { + if (object.ptr().getClass().isItem(object.ptr())) + { + return true; + } + return object.ptr().mRef->getType() == ESM::REC_LIGH + && (object.ptr().get()->mBase->mData.mFlags & ESM::Light::Carry) != 0; + }; + addItemDataBindings(item, context); } } From 92242a3d5420dd08e2900921d704d625ccce0cc5 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sun, 18 Feb 2024 13:20:58 -0600 Subject: [PATCH 093/451] Simplify --- apps/openmw/mwlua/types/item.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index ff05c3f232..7d6a5975e2 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -25,14 +25,7 @@ namespace MWLua item["isRestocking"] = [](const Object& object) -> bool { return object.ptr().getCellRef().getCount(false) < 0; }; - item["isCarriable"] = [](const Object& object) -> bool { - if (object.ptr().getClass().isItem(object.ptr())) - { - return true; - } - return object.ptr().mRef->getType() == ESM::REC_LIGH - && (object.ptr().get()->mBase->mData.mFlags & ESM::Light::Carry) != 0; - }; + item["isCarriable"] = [](const Object& object) -> bool { return object.ptr().getClass().isItem(object.ptr()); }; addItemDataBindings(item, context); } From fed62a851753b491be12e6be003b0173f4d8960b Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sun, 18 Feb 2024 15:58:18 -0600 Subject: [PATCH 094/451] Remove unneeded line --- apps/openmw/mwlua/types/item.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index 7d6a5975e2..8f05ce8e93 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -1,4 +1,3 @@ -#include #include #include "../../mwmechanics/spellutil.hpp" From 6f1710dee1ce5e1ecf0ae535070214c394acfe07 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 19 Feb 2024 16:14:52 +0400 Subject: [PATCH 095/451] Fix viewing distance spinbox in the launcher (bug 7840) --- CHANGELOG.md | 1 + apps/launcher/ui/settingspage.ui | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 671103cd21..509fa34b67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -144,6 +144,7 @@ Bug #7794: Fleeing NPCs name tooltip doesn't appear Bug #7796: Absorbed enchantments don't restore magicka Bug #7832: Ingredient tooltips show magnitude for Fortify Maximum Magicka effect + Bug #7840: First run of the launcher doesn't save viewing distance as the default value Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index ca23ec1bb4..5dd5cdc23d 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -663,6 +663,9 @@ + + 3 + cells @@ -670,7 +673,7 @@ 0.000000000000000 - 0.500000000000000 + 0.125000000000000 From ef730c439525c353c01d017dc0ff12cf3df44876 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 19 Feb 2024 18:26:58 +0100 Subject: [PATCH 096/451] Clean up global setting groups, don't try to destroy setting pages before they were rendered --- files/data/scripts/omw/settings/menu.lua | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 88913143e8..473e690e38 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -386,7 +386,9 @@ local function onGroupRegistered(global, key) if not pages[group.page] then return end if pageOptions[group.page] then - pageOptions[group.page].element:destroy() + if pageOptions[group.page].element then + pageOptions[group.page].element:destroy() + end else pageOptions[group.page] = {} end @@ -423,21 +425,24 @@ local menuPages = {} local function resetPlayerGroups() local playerGroupsSection = storage.playerSection(common.groupSectionKey) for pageKey, page in pairs(groups) do - for groupKey, group in pairs(page) do - if not menuGroups[groupKey] and not group.global then + for groupKey in pairs(page) do + if not menuGroups[groupKey] then page[groupKey] = nil playerGroupsSection:set(groupKey, nil) end end - if pageOptions[pageKey] then - pageOptions[pageKey].element:destroy() + local options = pageOptions[pageKey] + if options then + if options.element then + options.element:destroy() + end if not menuPages[pageKey] then - ui.removeSettingsPage(pageOptions[pageKey]) + ui.removeSettingsPage(options) pageOptions[pageKey] = nil else local renderedOptions = renderPage(pages[pageKey]) for k, v in pairs(renderedOptions) do - pageOptions[pageKey][k] = v + options[k] = v end end end From 586706ffe07cb859daa33da1c8dcb281c85c5ad6 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 19 Feb 2024 19:35:41 +0100 Subject: [PATCH 097/451] Handle group resets gracefully --- files/data/scripts/omw/settings/menu.lua | 46 +++++++++--------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 473e690e38..dc9ae6cba5 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -268,7 +268,7 @@ local function generateSearchHints(page) return table.concat(hints, ' ') end -local function renderPage(page) +local function renderPage(page, options) local l10n = core.l10n(page.l10n) local sortedGroups = {} for _, group in pairs(groups[page.key]) do @@ -329,11 +329,10 @@ local function renderPage(page) bigSpacer, }, } - return { - name = l10n(page.name), - element = ui.create(layout), - searchHints = generateSearchHints(page), - } + if options.element then options.element:destroy() end + options.name = l10n(page.name) + options.element = ui.create(layout) + options.searchHints = generateSearchHints(page) end local function onSettingChanged(global) @@ -341,8 +340,12 @@ local function onSettingChanged(global) local group = common.getSection(global, common.groupSectionKey):get(groupKey) if not group or not pageOptions[group.page] then return end - local value = common.getSection(global, group.key):get(settingKey) + if not settingKey then + renderPage(pages[group.page], pageOptions[group.page]) + return + end + local value = common.getSection(global, group.key):get(settingKey) local element = pageOptions[group.page].element local groupsLayout = element.layout.content.groups local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] @@ -385,17 +388,8 @@ local function onGroupRegistered(global, key) groups[group.page][pageGroup.key] = pageGroup if not pages[group.page] then return end - if pageOptions[group.page] then - if pageOptions[group.page].element then - pageOptions[group.page].element:destroy() - end - else - pageOptions[group.page] = {} - end - local renderedOptions = renderPage(pages[group.page]) - for k, v in pairs(renderedOptions) do - pageOptions[group.page][k] = v - end + pageOptions[group.page] = pageOptions[group.page] or {} + renderPage(pages[group.page], pageOptions[group.page]) end local function updateGroups(global) @@ -433,17 +427,14 @@ local function resetPlayerGroups() end local options = pageOptions[pageKey] if options then - if options.element then - options.element:destroy() - end if not menuPages[pageKey] then + if options.element then + options.element:destroy() + end ui.removeSettingsPage(options) pageOptions[pageKey] = nil else - local renderedOptions = renderPage(pages[pageKey]) - for k, v in pairs(renderedOptions) do - options[k] = v - end + renderPage(pages[pageKey], options) end end end @@ -477,10 +468,7 @@ local function registerPage(options) pageOptions[page.key].element:destroy() end pageOptions[page.key] = pageOptions[page.key] or {} - local renderedOptions = renderPage(page) - for k, v in pairs(renderedOptions) do - pageOptions[page.key][k] = v - end + renderPage(page, pageOptions[page.key]) ui.registerSettingsPage(pageOptions[page.key]) end From cfa6dc076b30656cc5699e892bb16f7dbfb980f9 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 19 Feb 2024 19:39:45 +0100 Subject: [PATCH 098/451] Render global setting groups on renderlua --- files/data/scripts/omw/settings/menu.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index dc9ae6cba5..704b29f032 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -407,10 +407,7 @@ local function updateGroups(global) end end)) end - local updatePlayerGroups = function() updateGroups(false) end -updatePlayerGroups() - local updateGlobalGroups = function() updateGroups(true) end local menuGroups = {} @@ -472,6 +469,11 @@ local function registerPage(options) ui.registerSettingsPage(pageOptions[page.key]) end +updatePlayerGroups() +if menu.getState() == menu.STATE.Running then -- handle reloadlua correctly + updateGlobalGroups() +end + return { interfaceName = 'Settings', interface = { From dc5371d157833b5cc834cfcaf5e460dc3af26504 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 22:28:33 +0100 Subject: [PATCH 099/451] Remove unused RipplesSurface::State::mOffset --- apps/openmw/mwrender/ripples.cpp | 1 - apps/openmw/mwrender/ripples.hpp | 1 - 2 files changed, 2 deletions(-) diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index dea372666e..6dcee1c9bd 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -136,7 +136,6 @@ namespace MWRender osg::Vec2f offset = mCurrentPlayerPos - mLastPlayerPos; mLastPlayerPos = mCurrentPlayerPos; mState[frameId].mPaused = mPaused; - mState[frameId].mOffset = offset; mState[frameId].mStateset->getUniform("positionCount")->set(static_cast(mPositionCount)); mState[frameId].mStateset->getUniform("offset")->set(offset); diff --git a/apps/openmw/mwrender/ripples.hpp b/apps/openmw/mwrender/ripples.hpp index 0d5b055eb5..6dd48e221f 100644 --- a/apps/openmw/mwrender/ripples.hpp +++ b/apps/openmw/mwrender/ripples.hpp @@ -54,7 +54,6 @@ namespace MWRender struct State { - osg::Vec2f mOffset; osg::ref_ptr mStateset; bool mPaused = true; }; From 56e69cf7a2626ce6c196c5543db100f834481765 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 22:30:51 +0100 Subject: [PATCH 100/451] Make some RipplesSurface members private --- apps/openmw/mwrender/ripples.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/ripples.hpp b/apps/openmw/mwrender/ripples.hpp index 6dd48e221f..41ca055174 100644 --- a/apps/openmw/mwrender/ripples.hpp +++ b/apps/openmw/mwrender/ripples.hpp @@ -50,6 +50,10 @@ namespace MWRender // e.g. texel to cell unit ratio static constexpr float mWorldScaleFactor = 2.5; + private: + void setupFragmentPipeline(); + void setupComputePipeline(); + Resource::ResourceSystem* mResourceSystem; struct State @@ -63,10 +67,6 @@ namespace MWRender std::array mState; - private: - void setupFragmentPipeline(); - void setupComputePipeline(); - osg::Vec2f mCurrentPlayerPos; osg::Vec2f mLastPlayerPos; From 3b01e209b1c180841eda565e491ad4680412cae7 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 22:31:50 +0100 Subject: [PATCH 101/451] Use proper names for static members --- apps/openmw/mwrender/ripples.cpp | 20 ++++++++++---------- apps/openmw/mwrender/ripples.hpp | 4 ++-- apps/openmw/mwrender/water.cpp | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index 6dcee1c9bd..7017101011 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -63,7 +63,7 @@ namespace MWRender stateset->addUniform(new osg::Uniform("offset", osg::Vec2f())); stateset->addUniform(new osg::Uniform("positionCount", 0)); stateset->addUniform(new osg::Uniform(osg::Uniform::Type::FLOAT_VEC3, "positions", 100)); - stateset->setAttributeAndModes(new osg::Viewport(0, 0, RipplesSurface::mRTTSize, RipplesSurface::mRTTSize)); + stateset->setAttributeAndModes(new osg::Viewport(0, 0, RipplesSurface::sRTTSize, RipplesSurface::sRTTSize)); mState[i].mStateset = stateset; } @@ -78,7 +78,7 @@ namespace MWRender texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_BORDER); texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_BORDER); texture->setBorderColor(osg::Vec4(0, 0, 0, 0)); - texture->setTextureSize(mRTTSize, mRTTSize); + texture->setTextureSize(sRTTSize, sRTTSize); mTextures[i] = texture; @@ -99,7 +99,7 @@ namespace MWRender { auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager(); - Shader::ShaderManager::DefineMap defineMap = { { "ripple_map_size", std::to_string(mRTTSize) + ".0" } }; + Shader::ShaderManager::DefineMap defineMap = { { "ripple_map_size", std::to_string(sRTTSize) + ".0" } }; osg::ref_ptr vertex = shaderManager.getShader("fullscreen_tri.vert", {}, osg::Shader::VERTEX); @@ -132,7 +132,7 @@ namespace MWRender const ESM::Position& playerPos = player.getRefData().getPosition(); mCurrentPlayerPos = osg::Vec2f( - std::floor(playerPos.pos[0] / mWorldScaleFactor), std::floor(playerPos.pos[1] / mWorldScaleFactor)); + std::floor(playerPos.pos[0] / sWorldScaleFactor), std::floor(playerPos.pos[1] / sWorldScaleFactor)); osg::Vec2f offset = mCurrentPlayerPos - mLastPlayerPos; mLastPlayerPos = mCurrentPlayerPos; mState[frameId].mPaused = mPaused; @@ -145,9 +145,9 @@ namespace MWRender { osg::Vec3f pos = mPositions[i] - osg::Vec3f( - mCurrentPlayerPos.x() * mWorldScaleFactor, mCurrentPlayerPos.y() * mWorldScaleFactor, 0.0) - + osg::Vec3f(mRTTSize * mWorldScaleFactor / 2, mRTTSize * mWorldScaleFactor / 2, 0.0); - pos /= mWorldScaleFactor; + mCurrentPlayerPos.x() * sWorldScaleFactor, mCurrentPlayerPos.y() * sWorldScaleFactor, 0.0) + + osg::Vec3f(sRTTSize * sWorldScaleFactor / 2, sRTTSize * sWorldScaleFactor / 2, 0.0); + pos /= sWorldScaleFactor; positions->setElement(i, pos); } positions->dirty(); @@ -195,7 +195,7 @@ namespace MWRender bindImage(mTextures[1], 0, GL_WRITE_ONLY_ARB); bindImage(mTextures[0], 1, GL_READ_ONLY_ARB); - ext.glDispatchCompute(mRTTSize / 16, mRTTSize / 16, 1); + ext.glDispatchCompute(sRTTSize / 16, sRTTSize / 16, 1); ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); } else @@ -217,7 +217,7 @@ namespace MWRender bindImage(mTextures[0], 0, GL_WRITE_ONLY_ARB); bindImage(mTextures[1], 1, GL_READ_ONLY_ARB); - ext.glDispatchCompute(mRTTSize / 16, mRTTSize / 16, 1); + ext.glDispatchCompute(sRTTSize / 16, sRTTSize / 16, 1); ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); } else @@ -270,7 +270,7 @@ namespace MWRender setReferenceFrame(osg::Camera::ABSOLUTE_RF); setNodeMask(Mask_RenderToTexture); setClearMask(GL_NONE); - setViewport(0, 0, RipplesSurface::mRTTSize, RipplesSurface::mRTTSize); + setViewport(0, 0, RipplesSurface::sRTTSize, RipplesSurface::sRTTSize); addChild(mRipples); setCullingActive(false); setImplicitBufferAttachmentMask(0, 0); diff --git a/apps/openmw/mwrender/ripples.hpp b/apps/openmw/mwrender/ripples.hpp index 41ca055174..3559c164a6 100644 --- a/apps/openmw/mwrender/ripples.hpp +++ b/apps/openmw/mwrender/ripples.hpp @@ -46,9 +46,9 @@ namespace MWRender void releaseGLObjects(osg::State* state) const override; - static constexpr size_t mRTTSize = 1024; + static constexpr size_t sRTTSize = 1024; // e.g. texel to cell unit ratio - static constexpr float mWorldScaleFactor = 2.5; + static constexpr float sWorldScaleFactor = 2.5; private: void setupFragmentPipeline(); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 9fdb0583a2..283ec85c48 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -704,8 +704,8 @@ namespace MWRender defineMap["refraction_enabled"] = std::string(mRefraction ? "1" : "0"); const int rippleDetail = Settings::water().mRainRippleDetail; defineMap["rain_ripple_detail"] = std::to_string(rippleDetail); - defineMap["ripple_map_world_scale"] = std::to_string(RipplesSurface::mWorldScaleFactor); - defineMap["ripple_map_size"] = std::to_string(RipplesSurface::mRTTSize) + ".0"; + defineMap["ripple_map_world_scale"] = std::to_string(RipplesSurface::sWorldScaleFactor); + defineMap["ripple_map_size"] = std::to_string(RipplesSurface::sRTTSize) + ".0"; Stereo::shaderStereoDefines(defineMap); From 2c1c8bc8de0b12f71152d822c04e939bb6f0a7de Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 19 Feb 2024 23:16:50 +0000 Subject: [PATCH 102/451] Work around for listAllAvailablePlugins --- components/misc/osgpluginchecker.cpp.in | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index 9bc165c5d6..4b89551206 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -4,6 +4,8 @@ #include #include +#include +#include #include #include @@ -30,6 +32,31 @@ namespace Misc bool checkRequiredOSGPluginsArePresent() { + // work around osgDB::listAllAvailablePlugins() not working on some platforms due to a suspected OSG bug + std::filesystem::path pluginDirectoryName = std::string("osgPlugins-") + std::string(osgGetVersion()); + osgDB::FilePathList& filepath = osgDB::getLibraryFilePathList(); + for (const auto& path : filepath) + { +#ifdef OSG_USE_UTF8_FILENAME + std::filesystem::path osgPath {stringToU8String(path)}; +#else + std::filesystem::path osgPath {path}; +#endif + if (!osgPath.has_filename()) + osgPath = osgPath.parent_path(); + + if (osgPath.filename() == pluginDirectoryName) + { + osgPath = osgPath.parent_path(); +#ifdef OSG_USE_UTF8_FILENAME + std::string extraPath = u8StringToString(osgPath.u8string_view()); +#else + std::string extraPath = osgPath.string(); +#endif + filepath.emplace_back(std::move(extraPath)); + } + } + auto availableOSGPlugins = osgDB::listAllAvailablePlugins(); bool haveAllPlugins = true; for (std::string_view plugin : USED_OSG_PLUGIN_FILENAMES) From c9b4c8632a4bcddb260324bdc53b747c23d7c881 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 23:14:40 +0100 Subject: [PATCH 103/451] Update ripples surface only when there is need to do so This depends on the difference between FPS which is dynamic and ripples update frequency which is contant. If FPS > ripples update frequency, some frames do nothing. If FPS <= ripples update frequency each frame runs shaders once. Update offset, possitions shader uniforms only when it will be run. --- apps/openmw/mwrender/ripples.cpp | 139 +++++++++++++++++-------------- apps/openmw/mwrender/ripples.hpp | 18 ++-- 2 files changed, 89 insertions(+), 68 deletions(-) diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index 7017101011..94135eeec5 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -119,58 +119,83 @@ namespace MWRender nullptr, shaderManager.getShader("core/ripples_simulate.comp", {}, osg::Shader::COMPUTE)); } - void RipplesSurface::traverse(osg::NodeVisitor& nv) + void RipplesSurface::updateState(const osg::FrameStamp& frameStamp, State& state) { - if (!nv.getFrameStamp()) + state.mPaused = mPaused; + + if (mPaused) return; - if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + constexpr double updateFrequency = 60.0; + constexpr double updatePeriod = 1.0 / updateFrequency; + + const double simulationTime = frameStamp.getSimulationTime(); + const double frameDuration = simulationTime - mLastSimulationTime; + mLastSimulationTime = simulationTime; + + mRemainingWaveTime += frameDuration; + const double ticks = std::floor(mRemainingWaveTime * updateFrequency); + mRemainingWaveTime -= ticks * updatePeriod; + + if (ticks == 0) { - size_t frameId = nv.getFrameStamp()->getFrameNumber() % 2; + state.mPaused = true; + return; + } - const auto& player = MWMechanics::getPlayer(); - const ESM::Position& playerPos = player.getRefData().getPosition(); + const MWWorld::Ptr player = MWMechanics::getPlayer(); + const ESM::Position& playerPos = player.getRefData().getPosition(); - mCurrentPlayerPos = osg::Vec2f( - std::floor(playerPos.pos[0] / sWorldScaleFactor), std::floor(playerPos.pos[1] / sWorldScaleFactor)); - osg::Vec2f offset = mCurrentPlayerPos - mLastPlayerPos; - mLastPlayerPos = mCurrentPlayerPos; - mState[frameId].mPaused = mPaused; - mState[frameId].mStateset->getUniform("positionCount")->set(static_cast(mPositionCount)); - mState[frameId].mStateset->getUniform("offset")->set(offset); + mCurrentPlayerPos = osg::Vec2f( + std::floor(playerPos.pos[0] / sWorldScaleFactor), std::floor(playerPos.pos[1] / sWorldScaleFactor)); + const osg::Vec2f offset = mCurrentPlayerPos - mLastPlayerPos; + mLastPlayerPos = mCurrentPlayerPos; - auto* positions = mState[frameId].mStateset->getUniform("positions"); + state.mStateset->getUniform("positionCount")->set(static_cast(mPositionCount)); + state.mStateset->getUniform("offset")->set(offset); - for (size_t i = 0; i < mPositionCount; ++i) - { - osg::Vec3f pos = mPositions[i] - - osg::Vec3f( - mCurrentPlayerPos.x() * sWorldScaleFactor, mCurrentPlayerPos.y() * sWorldScaleFactor, 0.0) - + osg::Vec3f(sRTTSize * sWorldScaleFactor / 2, sRTTSize * sWorldScaleFactor / 2, 0.0); - pos /= sWorldScaleFactor; - positions->setElement(i, pos); - } - positions->dirty(); + osg::Uniform* const positions = state.mStateset->getUniform("positions"); - mPositionCount = 0; + for (std::size_t i = 0; i < mPositionCount; ++i) + { + osg::Vec3f pos = mPositions[i] + - osg::Vec3f(mCurrentPlayerPos.x() * sWorldScaleFactor, mCurrentPlayerPos.y() * sWorldScaleFactor, 0.0) + + osg::Vec3f(sRTTSize * sWorldScaleFactor / 2, sRTTSize * sWorldScaleFactor / 2, 0.0); + pos /= sWorldScaleFactor; + positions->setElement(i, pos); } + positions->dirty(); + + mPositionCount = 0; + } + + void RipplesSurface::traverse(osg::NodeVisitor& nv) + { + const osg::FrameStamp* const frameStamp = nv.getFrameStamp(); + + if (frameStamp == nullptr) + return; + + if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) + updateState(*frameStamp, mState[frameStamp->getFrameNumber() % 2]); + osg::Geometry::traverse(nv); } void RipplesSurface::drawImplementation(osg::RenderInfo& renderInfo) const { osg::State& state = *renderInfo.getState(); - osg::GLExtensions& ext = *state.get(); - size_t contextID = state.getContextID(); - - size_t currentFrame = state.getFrameStamp()->getFrameNumber() % 2; + const std::size_t currentFrame = state.getFrameStamp()->getFrameNumber() % 2; const State& frameState = mState[currentFrame]; if (frameState.mPaused) { return; } - auto bindImage = [contextID, &state, &ext](osg::Texture2D* texture, GLuint index, GLenum access) { + osg::GLExtensions& ext = *state.get(); + const std::size_t contextID = state.getContextID(); + + const auto bindImage = [&](osg::Texture2D* texture, GLuint index, GLenum access) { osg::Texture::TextureObject* to = texture->getTextureObject(contextID); if (!to || texture->isDirty(contextID)) { @@ -180,52 +205,42 @@ namespace MWRender ext.glBindImageTexture(index, to->id(), 0, GL_FALSE, 0, access, GL_RGBA16F); }; - // Run simulation at a fixed rate independent on current FPS - // FIXME: when we skip frames we need to preserve positions. this doesn't work now - size_t ticks = 1; - // PASS: Blot in all ripple spawners mProgramBlobber->apply(state); state.apply(frameState.mStateset); - for (size_t i = 0; i < ticks; i++) + if (mUseCompute) { - if (mUseCompute) - { - bindImage(mTextures[1], 0, GL_WRITE_ONLY_ARB); - bindImage(mTextures[0], 1, GL_READ_ONLY_ARB); + bindImage(mTextures[1], 0, GL_WRITE_ONLY_ARB); + bindImage(mTextures[0], 1, GL_READ_ONLY_ARB); - ext.glDispatchCompute(sRTTSize / 16, sRTTSize / 16, 1); - ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); - } - else - { - mFBOs[1]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); - state.applyTextureAttribute(0, mTextures[0]); - osg::Geometry::drawImplementation(renderInfo); - } + ext.glDispatchCompute(sRTTSize / 16, sRTTSize / 16, 1); + ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); + } + else + { + mFBOs[1]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + state.applyTextureAttribute(0, mTextures[0]); + osg::Geometry::drawImplementation(renderInfo); } // PASS: Wave simulation mProgramSimulation->apply(state); state.apply(frameState.mStateset); - for (size_t i = 0; i < ticks; i++) + if (mUseCompute) { - if (mUseCompute) - { - bindImage(mTextures[0], 0, GL_WRITE_ONLY_ARB); - bindImage(mTextures[1], 1, GL_READ_ONLY_ARB); + bindImage(mTextures[0], 0, GL_WRITE_ONLY_ARB); + bindImage(mTextures[1], 1, GL_READ_ONLY_ARB); - ext.glDispatchCompute(sRTTSize / 16, sRTTSize / 16, 1); - ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); - } - else - { - mFBOs[0]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); - state.applyTextureAttribute(0, mTextures[1]); - osg::Geometry::drawImplementation(renderInfo); - } + ext.glDispatchCompute(sRTTSize / 16, sRTTSize / 16, 1); + ext.glMemoryBarrier(GL_ALL_BARRIER_BITS); + } + else + { + mFBOs[0]->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + state.applyTextureAttribute(0, mTextures[1]); + osg::Geometry::drawImplementation(renderInfo); } } diff --git a/apps/openmw/mwrender/ripples.hpp b/apps/openmw/mwrender/ripples.hpp index 3559c164a6..e355b16ecd 100644 --- a/apps/openmw/mwrender/ripples.hpp +++ b/apps/openmw/mwrender/ripples.hpp @@ -51,17 +51,20 @@ namespace MWRender static constexpr float sWorldScaleFactor = 2.5; private: - void setupFragmentPipeline(); - void setupComputePipeline(); - - Resource::ResourceSystem* mResourceSystem; - struct State { - osg::ref_ptr mStateset; bool mPaused = true; + osg::ref_ptr mStateset; }; + void setupFragmentPipeline(); + + void setupComputePipeline(); + + inline void updateState(const osg::FrameStamp& frameStamp, State& state); + + Resource::ResourceSystem* mResourceSystem; + size_t mPositionCount = 0; std::array mPositions; @@ -78,6 +81,9 @@ namespace MWRender bool mPaused = false; bool mUseCompute = false; + + double mLastSimulationTime = 0; + double mRemainingWaveTime = 0; }; class Ripples : public osg::Camera From 3971abf5e6e6c09717e5ff00bc3d0b20d6fe9be9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 20 Feb 2024 14:02:59 +0400 Subject: [PATCH 104/451] Minor launcher improvements (feature 7843) --- apps/launcher/settingspage.cpp | 13 ++++- apps/launcher/settingspage.hpp | 1 + apps/launcher/ui/settingspage.ui | 82 +++++++++++++++++--------------- files/lang/launcher_de.ts | 30 +++++------- files/lang/launcher_fr.ts | 30 +++++------- files/lang/launcher_ru.ts | 18 +++---- 6 files changed, 89 insertions(+), 85 deletions(-) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 0b5f542888..93a724909e 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -193,8 +193,10 @@ bool Launcher::SettingsPage::loadSettings() loadSettingBool(Settings::game().mSmoothMovement, *smoothMovementCheckBox); loadSettingBool(Settings::game().mPlayerMovementIgnoresAnimation, *playerMovementIgnoresAnimationCheckBox); - distantLandCheckBox->setCheckState( - Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging ? Qt::Checked : Qt::Unchecked); + connect(distantLandCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotDistantLandToggled); + bool distantLandEnabled = Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging; + distantLandCheckBox->setCheckState(distantLandEnabled ? Qt::Checked : Qt::Unchecked); + slotDistantLandToggled(distantLandEnabled); loadSettingBool(Settings::terrain().mObjectPagingActiveGrid, *activeGridObjectPagingCheckBox); viewingDistanceComboBox->setValue(convertToCells(Settings::camera().mViewingDistance)); @@ -583,9 +585,16 @@ void Launcher::SettingsPage::slotShadowDistLimitToggled(bool checked) fadeStartSpinBox->setEnabled(checked); } +void Launcher::SettingsPage::slotDistantLandToggled(bool checked) +{ + activeGridObjectPagingCheckBox->setEnabled(checked); + objectPagingMinSizeComboBox->setEnabled(checked); +} + void Launcher::SettingsPage::slotLightTypeCurrentIndexChanged(int index) { lightsMaximumDistanceSpinBox->setEnabled(index != 0); + lightFadeMultiplierSpinBox->setEnabled(index != 0); lightsMaxLightsSpinBox->setEnabled(index != 0); lightsBoundingSphereMultiplierSpinBox->setEnabled(index != 0); lightsMinimumInteriorBrightnessSpinBox->setEnabled(index != 0); diff --git a/apps/launcher/settingspage.hpp b/apps/launcher/settingspage.hpp index ea675857ea..652b8ce82d 100644 --- a/apps/launcher/settingspage.hpp +++ b/apps/launcher/settingspage.hpp @@ -33,6 +33,7 @@ namespace Launcher void slotPostProcessToggled(bool checked); void slotSkyBlendingToggled(bool checked); void slotShadowDistLimitToggled(bool checked); + void slotDistantLandToggled(bool checked); void slotLightTypeCurrentIndexChanged(int index); private: diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 5dd5cdc23d..665c9e9712 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -652,23 +652,46 @@ - + - <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> - Distant land + Object paging min size - - + + 3 + + 0.000000000000000 + + + 0.250000000000000 + + + 0.005000000000000 + + + + + + + Viewing distance + + + + + cells + + 3 + 0.000000000000000 @@ -677,7 +700,7 @@ - + Qt::Vertical @@ -691,39 +714,16 @@ - + - <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> - - - Object paging min size + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - - - - - Viewing distance + Distant land - - - 3 - - - 0.000000000000000 - - - 0.250000000000000 - - - 0.005000000000000 - - - - <html><head/><body><p>Use object paging for active cells grid.</p></body></html> @@ -869,6 +869,9 @@ 81920 + + 128 + 8192 @@ -972,6 +975,9 @@ 1.000000000000000 + + 0.010000000000000 + 0.900000000000000 @@ -1027,7 +1033,7 @@ <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - Lights maximum distance + Maximum light distance @@ -1050,7 +1056,7 @@ <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - Max light sources + Max lights @@ -1060,7 +1066,7 @@ <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - Lights fade multiplier + Fade start multiplier @@ -1102,7 +1108,7 @@ <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> - Lights bounding sphere multiplier + Bounding sphere multiplier @@ -1112,7 +1118,7 @@ <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - Lights minimum interior brightness + Minimum interior brightness @@ -1323,7 +1329,7 @@ - + In third-person view, use the camera as the sound listener instead of the player character. diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 11dd865c56..925733f9d3 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -1396,26 +1396,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - - Lights maximum distance - - <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - - Max light sources - - <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - - Lights fade multiplier - - <html><head/><body><p>Set the internal handling of light sources.</p> <p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> @@ -1436,23 +1424,31 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Lights bounding sphere multiplier + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + In third-person view, use the camera as the sound listener instead of the player character. - Lights minimum interior brightness + Use the camera as the sound listener - In third-person view, use the camera as the sound listener instead of the player character. + Maximum light distance - Use the camera as the sound listener + Max lights + + + + Bounding sphere multiplier + + + + Minimum interior brightness diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 5df4822808..3471fc6c5c 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -1396,26 +1396,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - - Lights maximum distance - - <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - - Max light sources - - <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - - Lights fade multiplier - - <html><head/><body><p>Set the internal handling of light sources.</p> <p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> @@ -1436,23 +1424,31 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Lights bounding sphere multiplier + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + In third-person view, use the camera as the sound listener instead of the player character. - Lights minimum interior brightness + Use the camera as the sound listener - In third-person view, use the camera as the sound listener instead of the player character. + Maximum light distance - Use the camera as the sound listener + Max lights + + + + Bounding sphere multiplier + + + + Minimum interior brightness diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 1db804e2df..ec7aeccc57 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -1409,7 +1409,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Максимальное расстояние, на котором будут отображаться источники света (во внутриигровых единицах измерения).</p><p>Если 0, то расстояние не ограничено.</p></body></html> - Lights maximum distance + Maximum light distance Дальность отображения источников света @@ -1417,17 +1417,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Максимальное количество источников света для каждого объекта.</p><p>Низкие числа (близкие к значению по умолчанию) приводят к резким перепадам освещения, как при устаревшем методе освещения.</p></body></html> - Max light sources + Max lights Макс. кол-во источников света <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> <html><head/><body><p>Доля расстояния (относительно дальности отображения источников света), на которой свет начинает затухать.</p><p>Низкие значения ведут к плавному затуханию, высокие - к резкому.</p></body></html> - - Lights fade multiplier - Множитель начала затухания - <html><head/><body><p>Set the internal handling of light sources.</p> <p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> @@ -1451,17 +1447,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Множитель размера ограничивающей сферы источников света.</p><p>Высокие значения делают затухание света плавнее, но требуют более высокого максимального количества источников света.</p><p>Настройка не влияет на уровень освещения или мощность источников света.</p></body></html> - Lights bounding sphere multiplier + Bounding sphere multiplier Множитель размера ограничивающей сферы <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> <html><head/><body><p>Минимальный уровень фонового освещения в помещениях.</p><p>Увеличьте значение, если помещения в игре кажутся слишком темными.</p></body></html> - - Lights minimum interior brightness - Минимальный уровень освещения в помещениях - In third-person view, use the camera as the sound listener instead of the player character. Использовать в виде от третьего лица положение камеры, а не персонажа игрока для прослушивания звуков. @@ -1470,5 +1462,9 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Use the camera as the sound listener Использовать камеру как слушателя + + Minimum interior brightness + Минимальный уровень освещения в помещениях + From 254b5335124abc8d93a31745df5a82c8a733ba4e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 20 Feb 2024 20:04:28 +0100 Subject: [PATCH 105/451] Allow the NAM9 field to be used if COUN is omitted --- components/esm3/objectstate.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index 7d26f431d6..25cbdc9a98 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -30,10 +30,7 @@ namespace ESM esm.getHNOT(mEnabled, "ENAB"); if (mVersion <= MaxOldCountFormatVersion) - { - mRef.mCount = 1; esm.getHNOT(mRef.mCount, "COUN"); - } mPosition = mRef.mPos; esm.getHNOT("POS_", mPosition.pos, mPosition.rot); From ccb506385f93ea179fc856e5d3569dd607fa5de3 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 20 Feb 2024 13:07:44 -0600 Subject: [PATCH 106/451] Fix player looking/controls --- apps/openmw/mwinput/mousemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index ffbe40a2db..0a179bd259 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -220,7 +220,7 @@ namespace MWInput }; // Only actually turn player when we're not in vanity mode - bool controls = MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols"); + bool controls = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && controls) { MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); From b4c5a2777a43465704b7382d957c6b9d6f74693a Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 20 Feb 2024 13:20:09 -0600 Subject: [PATCH 107/451] Rename var --- apps/openmw/mwinput/mousemanager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 0a179bd259..f18ec2ac87 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -220,14 +220,14 @@ namespace MWInput }; // Only actually turn player when we're not in vanity mode - bool controls = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); - if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && controls) + bool playerLooking = MWBase::Environment::get().getInputManager()->getControlSwitch("playerlooking"); + if (!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot) && playerLooking) { MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); player.yaw(-rot[2]); player.pitch(-rot[0]); } - else if (!controls) + else if (!playerLooking) MWBase::Environment::get().getWorld()->disableDeferredPreviewRotation(); MWBase::Environment::get().getInputManager()->resetIdleTime(); From d7bd50e45fc5d020358824ec312f998f807eaf3b Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 20 Feb 2024 20:20:38 +0100 Subject: [PATCH 108/451] Only set controls.jump to true for one frame when jumping --- files/data/scripts/omw/input/playercontrols.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/files/data/scripts/omw/input/playercontrols.lua b/files/data/scripts/omw/input/playercontrols.lua index 311b5a16a9..202e604087 100644 --- a/files/data/scripts/omw/input/playercontrols.lua +++ b/files/data/scripts/omw/input/playercontrols.lua @@ -83,6 +83,7 @@ end local movementControlsOverridden = false local autoMove = false +local attemptToJump = false local function processMovement() local movement = input.getRangeActionValue('MoveForward') - input.getRangeActionValue('MoveBackward') local sideMovement = input.getRangeActionValue('MoveRight') - input.getRangeActionValue('MoveLeft') @@ -97,6 +98,7 @@ local function processMovement() self.controls.movement = movement self.controls.sideMovement = sideMovement self.controls.run = run + self.controls.jump = attemptToJump if not settings:get('toggleSneak') then self.controls.sneak = input.getBooleanActionValue('Sneak') @@ -115,7 +117,7 @@ end input.registerTriggerHandler('Jump', async:callback(function() if not movementAllowed() then return end - self.controls.jump = Player.getControlSwitch(self, Player.CONTROL_SWITCH.Jumping) + attemptToJump = Player.getControlSwitch(self, Player.CONTROL_SWITCH.Jumping) end)) input.registerTriggerHandler('ToggleSneak', async:callback(function() @@ -223,6 +225,7 @@ local function onFrame(_) if combatAllowed() then processAttacking() end + attemptToJump = false end local function onSave() From 535c5e328a7c11b6e36364e5e6f640c2708ae4fa Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 20 Feb 2024 21:02:31 +0000 Subject: [PATCH 109/451] Affect correct texture units when disabling shadows for stateset Knowing which are right required making the function non-static, so the shadow manager had to become a singleton as the results of passing it around to where it's needed were hellish. I'm seeing a bunch of OpenGL errors when actually using this, so I'll investigate whether they're happening on master. I'm hesitant to look into it too much, though, as I'm affected by https://gitlab.com/OpenMW/openmw/-/issues/7811, and also have the Windows setting enabled that turns driver timeouts into a BSOD so a kernel dump is collected that I can send to AMD. --- apps/openmw/mwrender/characterpreview.cpp | 2 +- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/sky.cpp | 4 +-- apps/openmw/mwrender/water.cpp | 5 ++-- components/sceneutil/shadow.cpp | 36 +++++++++++++++-------- components/sceneutil/shadow.hpp | 10 +++++-- 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 9914aec7ca..a4c0181d35 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -247,7 +247,7 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*stateset); // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 892a8b5428..9e934d6f20 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -763,7 +763,7 @@ namespace MWRender lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*stateset); // override sun for local map SceneUtil::configureStateSetSunOverride(static_cast(mSceneRoot), light, stateset); diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index af41d2c590..9c8b0658a9 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -220,7 +220,7 @@ namespace camera->setNodeMask(MWRender::Mask_RenderToTexture); camera->setCullMask(MWRender::Mask_Sky); camera->addChild(mEarlyRenderBinRoot); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*camera->getOrCreateStateSet()); } private: @@ -274,7 +274,7 @@ namespace MWRender if (!mSceneManager->getForceShaders()) skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *skyroot->getOrCreateStateSet()); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*skyroot->getOrCreateStateSet()); parentNode->addChild(skyroot); mEarlyRenderBinRoot = new osg::Group; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 9fdb0583a2..35c10b81f4 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -276,8 +276,7 @@ namespace MWRender camera->setNodeMask(Mask_RenderToTexture); if (Settings::water().mRefractionScale != 1) // TODO: to be removed with issue #5709 - SceneUtil::ShadowManager::disableShadowsForStateSet( - Settings::shadows(), *camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override @@ -353,7 +352,7 @@ namespace MWRender camera->addChild(mClipCullNode); camera->setNodeMask(Mask_RenderToTexture); - SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 04f3b65edd..273016501d 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -13,6 +13,16 @@ namespace SceneUtil { using namespace osgShadow; + ShadowManager* ShadowManager::sInstance = nullptr; + + const ShadowManager& ShadowManager::instance() + { + if (sInstance) + return *sInstance; + else + throw std::logic_error("No ShadowManager exists yet"); + } + void ShadowManager::setupShadowSettings( const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager) { @@ -75,15 +85,11 @@ namespace SceneUtil mShadowTechnique->disableDebugHUD(); } - void ShadowManager::disableShadowsForStateSet(const Settings::ShadowsCategory& settings, osg::StateSet& stateset) + void ShadowManager::disableShadowsForStateSet(osg::StateSet& stateset) const { - if (!settings.mEnableShadows) + if (!mEnableShadows) return; - const int numberOfShadowMapsPerLight = settings.mNumberOfShadowMaps; - - int baseShadowTextureUnit = 8 - numberOfShadowMapsPerLight; - osg::ref_ptr fakeShadowMapImage = new osg::Image(); fakeShadowMapImage->allocateImage(1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT); *(float*)fakeShadowMapImage->data() = std::numeric_limits::infinity(); @@ -92,14 +98,15 @@ namespace SceneUtil fakeShadowMapTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); fakeShadowMapTexture->setShadowComparison(true); fakeShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); - for (int i = baseShadowTextureUnit; i < baseShadowTextureUnit + numberOfShadowMapsPerLight; ++i) + for (int i = mShadowSettings->getBaseShadowTextureUnit(); + i < mShadowSettings->getBaseShadowTextureUnit() + mShadowSettings->getNumShadowMapsPerLight(); ++i) { stateset.setTextureAttributeAndModes(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); - stateset.addUniform( - new osg::Uniform(("shadowTexture" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); - stateset.addUniform( - new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); + stateset.addUniform(new osg::Uniform( + ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), i)); + stateset.addUniform(new osg::Uniform( + ("shadowTextureUnit" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), i)); } } @@ -111,6 +118,9 @@ namespace SceneUtil , mOutdoorShadowCastingMask(outdoorShadowCastingMask) , mIndoorShadowCastingMask(indoorShadowCastingMask) { + if (sInstance) + throw std::logic_error("A ShadowManager already exists"); + mShadowedScene->setShadowTechnique(mShadowTechnique); if (Stereo::getStereo()) @@ -127,6 +137,8 @@ namespace SceneUtil mShadowTechnique->setWorldMask(worldMask); enableOutdoorMode(); + + sInstance = this; } ShadowManager::~ShadowManager() @@ -135,7 +147,7 @@ namespace SceneUtil Stereo::Manager::instance().setShadowTechnique(nullptr); } - Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines(const Settings::ShadowsCategory& settings) + Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines(const Settings::ShadowsCategory& settings) const { if (!mEnableShadows) return getShadowsDisabledDefines(); diff --git a/components/sceneutil/shadow.hpp b/components/sceneutil/shadow.hpp index fd82e828b6..952d750051 100644 --- a/components/sceneutil/shadow.hpp +++ b/components/sceneutil/shadow.hpp @@ -26,10 +26,10 @@ namespace SceneUtil class ShadowManager { public: - static void disableShadowsForStateSet(const Settings::ShadowsCategory& settings, osg::StateSet& stateset); - static Shader::ShaderManager::DefineMap getShadowsDisabledDefines(); + static const ShadowManager& instance(); + explicit ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, unsigned int worldMask, const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager); @@ -37,13 +37,17 @@ namespace SceneUtil void setupShadowSettings(const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager); - Shader::ShaderManager::DefineMap getShadowDefines(const Settings::ShadowsCategory& settings); + void disableShadowsForStateSet(osg::StateSet& stateset) const; + + Shader::ShaderManager::DefineMap getShadowDefines(const Settings::ShadowsCategory& settings) const; void enableIndoorMode(const Settings::ShadowsCategory& settings); void enableOutdoorMode(); protected: + static ShadowManager* sInstance; + bool mEnableShadows; osg::ref_ptr mShadowedScene; From 7391bf2814ce24683fb718f8367db8dbe799cba7 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 20 Feb 2024 21:23:23 +0000 Subject: [PATCH 110/451] Fix OpenGL errors There's no reason to use the AndModes variant as we never (intentionally) attempt to sample from a shadow map via the FFP. --- components/sceneutil/shadow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 273016501d..d1e4cf814c 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -101,7 +101,7 @@ namespace SceneUtil for (int i = mShadowSettings->getBaseShadowTextureUnit(); i < mShadowSettings->getBaseShadowTextureUnit() + mShadowSettings->getNumShadowMapsPerLight(); ++i) { - stateset.setTextureAttributeAndModes(i, fakeShadowMapTexture, + stateset.setTextureAttribute(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); stateset.addUniform(new osg::Uniform( ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), i)); From 132c43affa64c2d75678231bb2acdd4d9bd367c7 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 20 Feb 2024 22:14:13 +0000 Subject: [PATCH 111/451] Fix warning Also attempt to make an equivalent warning fire with MSVC, then have to fix other stuff because /WX wasn't working, then back out of enabling the warning because none of the ones I could find disliked the old code. --- CMakeLists.txt | 33 ++++++++++++++++----------------- components/sceneutil/shadow.cpp | 2 +- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f13def9ab0..d1ad7fa387 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -718,67 +718,66 @@ if (WIN32) ) foreach(d ${WARNINGS_DISABLE}) - set(WARNINGS "${WARNINGS} /wd${d}") + list(APPEND WARNINGS "/wd${d}") endforeach(d) if(OPENMW_MSVC_WERROR) - set(WARNINGS "${WARNINGS} /WX") + list(APPEND WARNINGS "/WX") endif() - set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS}") - set_target_properties(osg-ffmpeg-videoplayer PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(components PRIVATE ${WARNINGS}) + target_compile_options(osg-ffmpeg-videoplayer PRIVATE ${WARNINGS}) if (MSVC_VERSION GREATER_EQUAL 1915 AND MSVC_VERSION LESS 1920) target_compile_definitions(components INTERFACE _ENABLE_EXTENDED_ALIGNED_STORAGE) endif() if (BUILD_BSATOOL) - set_target_properties(bsatool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(bsatool PRIVATE ${WARNINGS}) endif() if (BUILD_ESMTOOL) - set_target_properties(esmtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(esmtool PRIVATE ${WARNINGS}) endif() if (BUILD_ESSIMPORTER) - set_target_properties(openmw-essimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-essimporter PRIVATE ${WARNINGS}) endif() if (BUILD_LAUNCHER) - set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-launcher PRIVATE ${WARNINGS}) endif() if (BUILD_MWINIIMPORTER) - set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-iniimporter PRIVATE ${WARNINGS}) endif() if (BUILD_OPENCS) - set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-cs PRIVATE ${WARNINGS}) endif() if (BUILD_OPENMW) - set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw PRIVATE ${WARNINGS}) endif() if (BUILD_WIZARD) - set_target_properties(openmw-wizard PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-wizard PRIVATE ${WARNINGS}) endif() if (BUILD_UNITTESTS) - set_target_properties(openmw_test_suite PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw_test_suite PRIVATE ${WARNINGS}) endif() if (BUILD_BENCHMARKS) - set_target_properties(openmw_detournavigator_navmeshtilescache_benchmark PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE ${WARNINGS}) endif() if (BUILD_NAVMESHTOOL) - set_target_properties(openmw-navmeshtool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-navmeshtool PRIVATE ${WARNINGS}) endif() if (BUILD_BULLETOBJECTTOOL) - set(WARNINGS "${WARNINGS} ${MT_BUILD}") - set_target_properties(openmw-bulletobjecttool PROPERTIES COMPILE_FLAGS "${WARNINGS}") + target_compile_options(openmw-bulletobjecttool PRIVATE ${WARNINGS} ${MT_BUILD}) endif() endif(MSVC) diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index d1e4cf814c..9351ec249e 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -98,7 +98,7 @@ namespace SceneUtil fakeShadowMapTexture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); fakeShadowMapTexture->setShadowComparison(true); fakeShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); - for (int i = mShadowSettings->getBaseShadowTextureUnit(); + for (unsigned int i = mShadowSettings->getBaseShadowTextureUnit(); i < mShadowSettings->getBaseShadowTextureUnit() + mShadowSettings->getNumShadowMapsPerLight(); ++i) { stateset.setTextureAttribute(i, fakeShadowMapTexture, From d282fdb77a7bfd6dd5a14dde30708b8f671c5ff8 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 20 Feb 2024 23:10:03 +0000 Subject: [PATCH 112/451] Eliminate unused uniform --- components/sceneutil/mwshadowtechnique.cpp | 9 --------- components/sceneutil/shadow.cpp | 2 -- files/shaders/compatibility/shadows_vertex.glsl | 1 - 3 files changed, 12 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 06930ebe59..d0c270971a 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -1025,7 +1025,6 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) { dummyState->setTextureAttribute(i, _fallbackShadowMapTexture, osg::StateAttribute::ON); dummyState->addUniform(new osg::Uniform(("shadowTexture" + std::to_string(i - baseUnit)).c_str(), i)); - dummyState->addUniform(new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseUnit)).c_str(), i)); } cv.pushStateSet(dummyState); @@ -1711,14 +1710,6 @@ void MWShadowTechnique::createShaders() for (auto& perFrameUniformList : _uniforms) perFrameUniformList.emplace_back(shadowTextureSampler.get()); } - - { - std::stringstream sstr; - sstr<<"shadowTextureUnit"< shadowTextureUnit = new osg::Uniform(sstr.str().c_str(),(int)(settings->getBaseShadowTextureUnit()+sm_i)); - for (auto& perFrameUniformList : _uniforms) - perFrameUniformList.emplace_back(shadowTextureUnit.get()); - } } switch(settings->getShaderHint()) diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 9351ec249e..b32be08386 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -105,8 +105,6 @@ namespace SceneUtil osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); stateset.addUniform(new osg::Uniform( ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), i)); - stateset.addUniform(new osg::Uniform( - ("shadowTextureUnit" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), i)); } } diff --git a/files/shaders/compatibility/shadows_vertex.glsl b/files/shaders/compatibility/shadows_vertex.glsl index a99a4a10e6..23fbc74988 100644 --- a/files/shaders/compatibility/shadows_vertex.glsl +++ b/files/shaders/compatibility/shadows_vertex.glsl @@ -3,7 +3,6 @@ #if SHADOWS @foreach shadow_texture_unit_index @shadow_texture_unit_list uniform mat4 shadowSpaceMatrix@shadow_texture_unit_index; - uniform int shadowTextureUnit@shadow_texture_unit_index; varying vec4 shadowSpaceCoords@shadow_texture_unit_index; #if @perspectiveShadowMaps From 8c92f6ee87f315255281c5ee7216b19a5de3d682 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 20 Feb 2024 23:10:23 +0000 Subject: [PATCH 113/451] Make uniform a signed int again --- components/sceneutil/shadow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index b32be08386..37a11031aa 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -104,7 +104,7 @@ namespace SceneUtil stateset.setTextureAttribute(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); stateset.addUniform(new osg::Uniform( - ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), i)); + ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), static_cast(i))); } } From 3335ccbc32facfe6afc8e6a433f8fa2bca2de23c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 20 Feb 2024 23:51:42 +0000 Subject: [PATCH 114/451] Capitulate --- components/sceneutil/shadow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 37a11031aa..0d68ccaa0f 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -104,7 +104,8 @@ namespace SceneUtil stateset.setTextureAttribute(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); stateset.addUniform(new osg::Uniform( - ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), static_cast(i))); + ("shadowTexture" + std::to_string(i - mShadowSettings->getBaseShadowTextureUnit())).c_str(), + static_cast(i))); } } From fc55b876648f08471cb7d5694b1bbf1093807616 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 20 Feb 2024 23:38:49 -0600 Subject: [PATCH 115/451] Put it in the right place, again --- files/lua_api/openmw/core.lua | 7 ------- files/lua_api/openmw/types.lua | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 169a41b2d2..330f0e20a0 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -24,13 +24,6 @@ -- @param #string eventName -- @param eventData ---- --- Send an event to menu scripts. --- @function [parent=#core] sendMenuEvent --- @param openmw.core#GameObject player --- @param #string eventName --- @param eventData - --- -- Simulation time in seconds. -- The number of simulation seconds passed in the game world since starting a new game. diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 56deb6d558..df9014cf04 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1017,6 +1017,13 @@ -- @usage -- Start a new quest, add it to the player's quest list but don't add any journal entries -- types.Player.quests(player)["ms_fargothring"].stage = 0 +--- +-- Send an event to menu scripts. +-- @function [parent=#Player] sendMenuEvent +-- @param openmw.core#GameObject player +-- @param #string eventName +-- @param eventData + --- -- @type PlayerQuest -- @field #string id The quest id. From d96340c9028795691943cdd044feedd881af9a09 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 20 Feb 2024 23:42:27 -0600 Subject: [PATCH 116/451] Return to original order --- files/lua_api/openmw/types.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index df9014cf04..bd5e64901b 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1017,13 +1017,6 @@ -- @usage -- Start a new quest, add it to the player's quest list but don't add any journal entries -- types.Player.quests(player)["ms_fargothring"].stage = 0 ---- --- Send an event to menu scripts. --- @function [parent=#Player] sendMenuEvent --- @param openmw.core#GameObject player --- @param #string eventName --- @param eventData - --- -- @type PlayerQuest -- @field #string id The quest id. @@ -1104,6 +1097,13 @@ -- @field #string texture Birth sign texture -- @field #list<#string> spells A read-only list containing the ids of all spells gained from this sign. +--- +-- Send an event to menu scripts. +-- @function [parent=#Player] sendMenuEvent +-- @param openmw.core#GameObject player +-- @param #string eventName +-- @param eventData + -------------------------------------------------------------------------------- -- @{#Armor} functions -- @field [parent=#types] #Armor Armor From 8ecf1a116a05352924dd43c6ec75fa5484d6b4b7 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 20 Feb 2024 14:38:08 +0300 Subject: [PATCH 117/451] Add missing .49 changelog entries --- CHANGELOG.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 509fa34b67..96e6de5648 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load Bug #6025: Subrecords cannot overlap records Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex + Bug #6146: Lua command `actor:setEquipment` doesn't trigger mwscripts when equipping or unequipping a scripted item Bug #6190: Unintuitive sun specularity time of day dependence Bug #6222: global map cell size can crash openmw if set to too high a value Bug #6313: Followers with high Fight can turn hostile @@ -56,6 +57,7 @@ Bug #6973: Fade in happens after the scene load and is shown Bug #6974: Only harmful effects are reflected Bug #6977: Sun damage implementation does not match research + Bug #6985: Issues with Magic Cards numbers readability Bug #6986: Sound magic effect does not make noise Bug #6987: Set/Mod Blindness should not darken the screen Bug #6992: Crossbow reloading doesn't look the same as in Morrowind @@ -76,7 +78,9 @@ Bug #7131: MyGUI log spam when post processing HUD is open Bug #7134: Saves with an invalid last generated RefNum can be loaded Bug #7163: Myar Aranath: Wheat breaks the GUI + Bug #7168: Fix average scene luminance Bug #7172: Current music playlist continues playing indefinitely if next playlist is empty + Bug #7202: Post-processing normals for terrain, water randomly stop rendering 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 @@ -136,6 +140,7 @@ Bug #7753: Editor: Actors Don't Scale According to Their Race Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive + Bug #7763: Bullet shape loading problems, assorted Bug #7765: OpenMW-CS: Touch Record option is broken Bug #7769: Sword of the Perithia: Broken NPCs Bug #7770: Sword of the Perithia: Script execution failure @@ -151,14 +156,15 @@ Feature #5492: Let rain and snow collide with statics Feature #5926: Refraction based on water depth Feature #5944: Option to use camera as sound listener - Feature #6149: Dehardcode Lua API_REVISION Feature #6152: Playing music via lua scripts Feature #6188: Specular lighting from point light sources Feature #6411: Support translations in openmw-launcher Feature #6447: Add LOD support to Object Paging Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds + Feature #6679: Design a custom Input Action API Feature #6726: Lua API for creating new objects + Feature #6727: Lua API for records of all object types Feature #6864: Lua file access API Feature #6922: Improve launcher appearance Feature #6933: Support high-resolution cursor textures @@ -171,10 +177,12 @@ Feature #7125: Remembering console commands between sessions Feature #7129: Add support for non-adaptive VSync Feature #7130: Ability to set MyGUI logging verbosity + Feature #7142: MWScript Lua API Feature #7148: Optimize string literal lookup in mwscript + Feature #7161: OpenMW-CS: Make adding and filtering TopicInfos easier Feature #7194: Ori to show texture paths Feature #7214: Searching in the in-game console - Feature #7284: Searching in the console with regex and toggleable case-sensitivity + Feature #7248: Searching in the console with regex and toggleable case-sensitivity Feature #7468: Factions API for Lua Feature #7477: NegativeLight Magic Effect flag Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field @@ -194,7 +202,10 @@ Feature #7795: Support MaxNumberRipples INI setting Feature #7805: Lua Menu context Task #5896: Do not use deprecated MyGUI properties + Task #6085: Replace boost::filesystem with std::filesystem + Task #6149: Dehardcode Lua API_REVISION Task #6624: Drop support for saves made prior to 0.45 + Task #7048: Get rid of std::bind Task #7113: Move from std::atoi to std::from_char Task #7117: Replace boost::scoped_array with std::vector Task #7151: Do not use std::strerror to get errno error message From 4ca3b83ecbf3f6034026348947bbb209932920b8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 21 Feb 2024 09:07:00 +0300 Subject: [PATCH 118/451] Lua docs: equipment -> getEquipment --- files/lua_api/openmw/types.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index d16f560a58..b089336e68 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -68,7 +68,7 @@ -- @field #number Ammunition --- --- Available @{#EQUIPMENT_SLOT} values. Used in `Actor.equipment(obj)` and `Actor.setEquipment(obj, eqp)`. +-- Available @{#EQUIPMENT_SLOT} values. Used in `Actor.getEquipment(obj)` and `Actor.setEquipment(obj, eqp)`. -- @field [parent=#Actor] #EQUIPMENT_SLOT EQUIPMENT_SLOT --- From fdd88fd2953c2beccf83c913487fc82055728657 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 21 Feb 2024 13:30:09 +0000 Subject: [PATCH 119/451] c h a n g e l o g --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 671103cd21..7bf9d48b5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ Bug #7084: Resurrecting an actor doesn't take into account base record changes Bug #7088: Deleting last save game of last character doesn't clear character name/details Bug #7092: BSA archives from higher priority directories don't take priority + Bug #7102: Some HQ Creatures mod models can hit the 8 texture slots limit with 0.48 Bug #7103: Multiple paths pointing to the same plugin but with different cases lead to automatically removed config entries Bug #7122: Teleportation to underwater should cancel active water walking effect Bug #7131: MyGUI log spam when post processing HUD is open From c2ac1ce046ff84df787f1584e8e6fff14c4359ac Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 21 Feb 2024 21:35:15 +0100 Subject: [PATCH 120/451] Use is_directory member function To reduce the number of syscalls. --- components/vfs/filesystemarchive.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index 01e5c2a1b5..3303c6656c 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -24,11 +24,11 @@ namespace VFS for (auto it = std::filesystem::begin(iterator), end = std::filesystem::end(iterator); it != end;) { - const auto& i = *it; + const std::filesystem::directory_entry& entry = *it; - if (!std::filesystem::is_directory(i)) + if (!entry.is_directory()) { - const std::filesystem::path& filePath = i.path(); + const std::filesystem::path& filePath = entry.path(); const std::string proper = Files::pathToUnicodeString(filePath); VFS::Path::Normalized searchable(std::string_view{ proper }.substr(prefix)); FileSystemArchiveFile file(filePath); @@ -43,7 +43,7 @@ namespace VFS // Exception thrown by the operator++ may not contain the context of the error like what exact path caused // the problem which makes it hard to understand what's going on when iteration happens over a directory // with thousands of files and subdirectories. - const std::filesystem::path prevPath = i.path(); + const std::filesystem::path prevPath = entry.path(); std::error_code ec; it.increment(ec); if (ec != std::error_code()) From 9fc66d5de613a9035c5d0b43890be9173339424d Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 21 Feb 2024 15:25:13 -0600 Subject: [PATCH 121/451] Fix(idvalidator): Allow any printable character in refIds --- apps/opencs/view/world/idvalidator.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index 6f790d20cb..b089c1df39 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -4,13 +4,7 @@ bool CSVWorld::IdValidator::isValid(const QChar& c, bool first) const { - if (c.isLetter() || c == '_') - return true; - - if (!first && (c.isDigit() || c.isSpace())) - return true; - - return false; + return c.isPrint() ? true : false; } CSVWorld::IdValidator::IdValidator(bool relaxed, QObject* parent) From f27564ec784f120e3871f10342788f90ef8261b6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 22 Feb 2024 00:16:41 +0000 Subject: [PATCH 122/451] Actually use the plane distances we just computed We don't get any of the speedup if we don't do this. We also forget about any objects nearer the camera than the previous value except the groundcover we're just about to deal with. Fixes https://gitlab.com/OpenMW/openmw/-/issues/7844 --- apps/openmw/mwrender/groundcover.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 8656af9d2f..4f99ee7560 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -95,6 +95,8 @@ namespace MWRender { // Other objects are likely cheaper and should let us skip all but a few groundcover instances cullVisitor.computeNearPlane(); + computedZNear = cullVisitor.getCalculatedNearPlane(); + computedZFar = cullVisitor.getCalculatedFarPlane(); if (dNear < computedZNear) { From 2a5f8d5bab6746e28e98ecf2fe3f35b844e91fae Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 22 Feb 2024 00:24:44 +0000 Subject: [PATCH 123/451] Skip the check on MacOS It doesn't work, the workaround isn't enough to make it work, I can't be bothered making a more powerful workaround, and it's impossible to *package* a MacOS build missing the plugins we need anyway, even if you can build and attempt to run it. --- components/misc/osgpluginchecker.cpp.in | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index 4b89551206..e58c6c59b2 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -15,11 +15,14 @@ namespace Misc { -#ifdef OSG_LIBRARY_STATIC +#if defined(OSG_LIBRARY_STATIC) || defined(__APPLE__) bool checkRequiredOSGPluginsArePresent() { // assume they were linked in at build time and CMake would have failed if they were missing + // true-ish for MacOS - they're copied into the package and that'd fail if they were missing, + // but if you don't actually make a MacOS package and run a local build, this won't notice. + // the workaround in the real implementation isn't powerful enough to make MacOS work, though. return true; } From 090a389febf317f3480d29ce8c11f9c320bfb827 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Thu, 22 Feb 2024 02:52:58 -0600 Subject: [PATCH 124/451] Cleanup(idvalidator): Just don't use isValid function and instead directly check if input is a printable char --- apps/opencs/view/world/idvalidator.cpp | 7 +------ apps/opencs/view/world/idvalidator.hpp | 3 --- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/apps/opencs/view/world/idvalidator.cpp b/apps/opencs/view/world/idvalidator.cpp index b089c1df39..078bd6bce5 100644 --- a/apps/opencs/view/world/idvalidator.cpp +++ b/apps/opencs/view/world/idvalidator.cpp @@ -2,11 +2,6 @@ #include -bool CSVWorld::IdValidator::isValid(const QChar& c, bool first) const -{ - return c.isPrint() ? true : false; -} - CSVWorld::IdValidator::IdValidator(bool relaxed, QObject* parent) : QValidator(parent) , mRelaxed(relaxed) @@ -86,7 +81,7 @@ QValidator::State CSVWorld::IdValidator::validate(QString& input, int& pos) cons { prevScope = false; - if (!isValid(*iter, first)) + if (!iter->isPrint()) return QValidator::Invalid; } } diff --git a/apps/opencs/view/world/idvalidator.hpp b/apps/opencs/view/world/idvalidator.hpp index e831542961..6b98d35672 100644 --- a/apps/opencs/view/world/idvalidator.hpp +++ b/apps/opencs/view/world/idvalidator.hpp @@ -13,9 +13,6 @@ namespace CSVWorld std::string mNamespace; mutable std::string mError; - private: - bool isValid(const QChar& c, bool first) const; - public: IdValidator(bool relaxed = false, QObject* parent = nullptr); ///< \param relaxed Relaxed rules for IDs that also functino as user visible text From 9c5f269e52b48a7dd1b6cba479289b3071942fb5 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Thu, 22 Feb 2024 02:54:04 -0600 Subject: [PATCH 125/451] Add changelog entry for #7721 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96e6de5648..3815905684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -133,6 +133,7 @@ Bug #7679: Scene luminance value flashes when toggling shaders Bug #7685: Corky sometimes doesn't follow Llovyn Andus Bug #7712: Casting doesn't support spells and enchantments with no effects + Bug #7721: CS: Special Chars Not Allowed in IDs Bug #7723: Assaulting vampires and werewolves shouldn't be a crime Bug #7724: Guards don't help vs werewolves Bug #7733: Launcher shows incorrect data paths when there's two plugins with the same name From 1b431bf63372220fab856d0ef8dbb01b25edd03f Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Mon, 19 Feb 2024 05:25:20 -0600 Subject: [PATCH 126/451] Fix(editor): Don't save dirty water height values --- components/esm3/loadcell.cpp | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 829cf9e916..5b8521f9a1 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -190,25 +190,15 @@ namespace ESM if (mData.mFlags & Interior) { - if (mWaterInt) - { - int32_t water = (mWater >= 0) ? static_cast(mWater + 0.5) : static_cast(mWater - 0.5); - esm.writeHNT("INTV", water); - } - else - { + // Try to avoid saving ambient information when it's unnecessary. + // This is to fix black lighting and flooded water + // in resaved cell records that lack this information. + if (mWaterInt && mWater != 0) esm.writeHNT("WHGT", mWater); - } - if (mData.mFlags & QuasiEx) esm.writeHNOCRefId("RGNN", mRegion); - else - { - // Try to avoid saving ambient lighting information when it's unnecessary. - // This is to fix black lighting in resaved cell records that lack this information. - if (mHasAmbi) - esm.writeHNT("AMBI", mAmbi, 16); - } + else if (mHasAmbi) + esm.writeHNT("AMBI", mAmbi, 16); } else { From bb35f0366a12c1eea5e68d7aeabe4e19aac66758 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 20 Feb 2024 07:12:42 -0600 Subject: [PATCH 127/451] Fix(loadcell): Save water height regardless of value, if the user actually adjusted it --- apps/opencs/model/world/nestedcoladapterimp.cpp | 3 +++ components/esm3/loadcell.cpp | 5 ++++- components/esm3/loadcell.hpp | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 13ae821a77..ea3a3bde26 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -996,7 +996,10 @@ namespace CSMWorld case 5: { if (isInterior && interiorWater) + { cell.mWater = value.toFloat(); + cell.setHasWater(true); + } else return; // return without saving break; diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 5b8521f9a1..f550c190cb 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -118,6 +118,7 @@ namespace ESM bool overriding = !mName.empty(); bool isLoaded = false; mHasAmbi = false; + mHasWater = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); @@ -133,6 +134,7 @@ namespace ESM float waterLevel; esm.getHT(waterLevel); mWaterInt = false; + mHasWater = true; if (!std::isfinite(waterLevel)) { if (!overriding) @@ -193,7 +195,7 @@ namespace ESM // Try to avoid saving ambient information when it's unnecessary. // This is to fix black lighting and flooded water // in resaved cell records that lack this information. - if (mWaterInt && mWater != 0) + if (mHasWater) esm.writeHNT("WHGT", mWater); if (mData.mFlags & QuasiEx) esm.writeHNOCRefId("RGNN", mRegion); @@ -323,6 +325,7 @@ namespace ESM mData.mY = 0; mHasAmbi = true; + mHasWater = true; mAmbi.mAmbient = 0; mAmbi.mSunlight = 0; mAmbi.mFog = 0; diff --git a/components/esm3/loadcell.hpp b/components/esm3/loadcell.hpp index bfabdd58f9..d54ba9573a 100644 --- a/components/esm3/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -112,6 +112,7 @@ namespace ESM , mHasAmbi(true) , mWater(0) , mWaterInt(false) + , mHasWater(false) , mMapColor(0) , mRefNumCounter(0) { @@ -132,6 +133,7 @@ namespace ESM float mWater; // Water level bool mWaterInt; + bool mHasWater; int32_t mMapColor; // Counter for RefNums. This is only used during content file editing and has no impact on gameplay. // It prevents overwriting previous refNums, even if they were deleted. @@ -163,6 +165,8 @@ namespace ESM bool hasWater() const { return ((mData.mFlags & HasWater) != 0) || isExterior(); } + void setHasWater(bool hasWater) { mHasWater = hasWater; } + bool hasAmbient() const { return mHasAmbi; } void setHasAmbient(bool hasAmbi) { mHasAmbi = hasAmbi; } From f95cad07f2a5fc7087d10a1a3ca13c28b1bccf62 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 20 Feb 2024 08:01:08 -0600 Subject: [PATCH 128/451] Cleanup(loadcell): Remove unused integer water flag --- components/esm3/loadcell.cpp | 3 --- components/esm3/loadcell.hpp | 2 -- 2 files changed, 5 deletions(-) diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index f550c190cb..1d54cd84bf 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -128,12 +128,10 @@ namespace ESM int32_t waterl; esm.getHT(waterl); mWater = static_cast(waterl); - mWaterInt = true; break; case fourCC("WHGT"): float waterLevel; esm.getHT(waterLevel); - mWaterInt = false; mHasWater = true; if (!std::isfinite(waterLevel)) { @@ -316,7 +314,6 @@ namespace ESM mName.clear(); mRegion = ESM::RefId(); mWater = 0; - mWaterInt = false; mMapColor = 0; mRefNumCounter = 0; diff --git a/components/esm3/loadcell.hpp b/components/esm3/loadcell.hpp index d54ba9573a..1397479154 100644 --- a/components/esm3/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -111,7 +111,6 @@ namespace ESM , mRegion(ESM::RefId()) , mHasAmbi(true) , mWater(0) - , mWaterInt(false) , mHasWater(false) , mMapColor(0) , mRefNumCounter(0) @@ -132,7 +131,6 @@ namespace ESM bool mHasAmbi; float mWater; // Water level - bool mWaterInt; bool mHasWater; int32_t mMapColor; // Counter for RefNums. This is only used during content file editing and has no impact on gameplay. From d04572ac847180c569aa793c6c80557762ce181d Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 20 Feb 2024 08:39:43 -0600 Subject: [PATCH 129/451] Cleanup(loadcell): Rename mHasWater to mHasWaterHeightSub for clarity. --- components/esm3/loadcell.cpp | 8 ++++---- components/esm3/loadcell.hpp | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 1d54cd84bf..473c4c7d72 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -118,7 +118,7 @@ namespace ESM bool overriding = !mName.empty(); bool isLoaded = false; mHasAmbi = false; - mHasWater = false; + mHasWaterHeightSub = false; while (!isLoaded && esm.hasMoreSubs()) { esm.getSubName(); @@ -132,7 +132,7 @@ namespace ESM case fourCC("WHGT"): float waterLevel; esm.getHT(waterLevel); - mHasWater = true; + mHasWaterHeightSub = true; if (!std::isfinite(waterLevel)) { if (!overriding) @@ -193,7 +193,7 @@ namespace ESM // Try to avoid saving ambient information when it's unnecessary. // This is to fix black lighting and flooded water // in resaved cell records that lack this information. - if (mHasWater) + if (mHasWaterHeightSub) esm.writeHNT("WHGT", mWater); if (mData.mFlags & QuasiEx) esm.writeHNOCRefId("RGNN", mRegion); @@ -322,7 +322,7 @@ namespace ESM mData.mY = 0; mHasAmbi = true; - mHasWater = true; + mHasWaterHeightSub = true; mAmbi.mAmbient = 0; mAmbi.mSunlight = 0; mAmbi.mFog = 0; diff --git a/components/esm3/loadcell.hpp b/components/esm3/loadcell.hpp index 1397479154..a22110be32 100644 --- a/components/esm3/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -111,7 +111,7 @@ namespace ESM , mRegion(ESM::RefId()) , mHasAmbi(true) , mWater(0) - , mHasWater(false) + , mHasWaterHeightSub(false) , mMapColor(0) , mRefNumCounter(0) { @@ -131,7 +131,7 @@ namespace ESM bool mHasAmbi; float mWater; // Water level - bool mHasWater; + bool mHasWaterHeightSub; int32_t mMapColor; // Counter for RefNums. This is only used during content file editing and has no impact on gameplay. // It prevents overwriting previous refNums, even if they were deleted. @@ -163,7 +163,7 @@ namespace ESM bool hasWater() const { return ((mData.mFlags & HasWater) != 0) || isExterior(); } - void setHasWater(bool hasWater) { mHasWater = hasWater; } + void setHasWater(bool hasWater) { mHasWaterHeightSub = hasWater; } bool hasAmbient() const { return mHasAmbi; } From b2b1c98396edb05888b0c59e3ebb5bfa1188e140 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 20 Feb 2024 22:23:53 -0600 Subject: [PATCH 130/451] fix(esmtool): Don't try to log a variable that doesn't exist --- apps/esmtool/record.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 245012ce13..b1185a4d33 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -612,7 +612,6 @@ namespace EsmTool } else std::cout << " Map Color: " << Misc::StringUtils::format("0x%08X", mData.mMapColor) << std::endl; - std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } From 7f67d2e805bfba82c84163f429d266a9c4d84826 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Thu, 22 Feb 2024 03:02:10 -0600 Subject: [PATCH 131/451] Add changelog entry for #7841 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96e6de5648..c4fbaf3166 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -150,6 +150,7 @@ Bug #7796: Absorbed enchantments don't restore magicka Bug #7832: Ingredient tooltips show magnitude for Fortify Maximum Magicka effect Bug #7840: First run of the launcher doesn't save viewing distance as the default value + Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty From ce2787e15e7581c2aae5cd5a0b40203a5b3fe017 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Thu, 22 Feb 2024 03:23:23 -0600 Subject: [PATCH 132/451] Cleanup(loadcell): Rename setHasWater to setHasWaterHeightSub --- apps/opencs/model/world/nestedcoladapterimp.cpp | 2 +- components/esm3/loadcell.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index ea3a3bde26..8b8c7b17be 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -998,7 +998,7 @@ namespace CSMWorld if (isInterior && interiorWater) { cell.mWater = value.toFloat(); - cell.setHasWater(true); + cell.setHasWaterHeightSub(true); } else return; // return without saving diff --git a/components/esm3/loadcell.hpp b/components/esm3/loadcell.hpp index a22110be32..3f16bcca31 100644 --- a/components/esm3/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -163,7 +163,7 @@ namespace ESM bool hasWater() const { return ((mData.mFlags & HasWater) != 0) || isExterior(); } - void setHasWater(bool hasWater) { mHasWaterHeightSub = hasWater; } + void setHasWaterHeightSub(bool hasWater) { mHasWaterHeightSub = hasWater; } bool hasAmbient() const { return mHasAmbi; } From 38990b1fd227acca441c98296a9841f878c559a2 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 22 Feb 2024 11:15:39 +0100 Subject: [PATCH 133/451] Set components property after it is defined --- components/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index dc195d8d0b..6ab7fc4795 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -526,16 +526,16 @@ if (USE_QT) QT_WRAP_UI(ESM_UI_HDR ${ESM_UI}) endif() -if (ANDROID) - set_property(TARGET components PROPERTY POSTION_INDEPENDENT_CODE ON) -endif() - include_directories(${BULLET_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) find_package(SQLite3 REQUIRED) add_library(components STATIC ${COMPONENT_FILES}) +if (ANDROID) + set_property(TARGET components PROPERTY POSITION_INDEPENDENT_CODE ON) +endif() + target_link_libraries(components ${COLLADA_DOM_LIBRARIES} From 7c4b42ab2a7cee4eec39fabf7ba8a95c2c735da0 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 22 Feb 2024 19:06:15 +0400 Subject: [PATCH 134/451] Add a Lua function to check if actor's death is finished --- CMakeLists.txt | 2 +- apps/openmw/mwlua/types/actor.cpp | 5 +++++ files/lua_api/openmw/types.lua | 8 +++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f13def9ab0..107a8691ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,7 +80,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 54) +set(OPENMW_LUA_API_REVISION 55) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 4fda04e7c5..3b0142e441 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -403,6 +403,11 @@ namespace MWLua return target.getClass().getCreatureStats(target).isDead(); }; + actor["isDeathFinished"] = [](const Object& o) { + const auto& target = o.ptr(); + return target.getClass().getCreatureStats(target).isDeathAnimationFinished(); + }; + actor["getEncumbrance"] = [](const Object& actor) -> float { const MWWorld::Ptr ptr = actor.ptr(); return ptr.getClass().getEncumbrance(ptr); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index a7f57d3a6c..0c51544f64 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -16,11 +16,17 @@ -- @return #number --- --- Check if the given actor is dead. +-- Check if the given actor is dead (health reached 0, so death process started). -- @function [parent=#Actor] isDead -- @param openmw.core#GameObject actor -- @return #boolean +--- +-- Check if the given actor's death process is finished. +-- @function [parent=#Actor] isDeathFinished +-- @param openmw.core#GameObject actor +-- @return #boolean + --- -- Agent bounds to be used for pathfinding functions. -- @function [parent=#Actor] getPathfindingAgentBounds From 0bab37327c0055120687d9db64ac04de99fd3249 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 22 Feb 2024 20:23:21 +0100 Subject: [PATCH 135/451] Account for pre-0.46 saves storing a gold value of 0 for everything --- components/esm3/formatversion.hpp | 1 + components/esm3/objectstate.cpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 9f499a7231..d90742a512 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -9,6 +9,7 @@ namespace ESM inline constexpr FormatVersion DefaultFormatVersion = 0; inline constexpr FormatVersion CurrentContentFormatVersion = 1; + inline constexpr FormatVersion MaxOldGoldValueFormatVersion = 5; inline constexpr FormatVersion MaxOldFogOfWarFormatVersion = 6; inline constexpr FormatVersion MaxUnoptimizedCharacterDataFormatVersion = 7; inline constexpr FormatVersion MaxOldTimeLeftFormatVersion = 8; diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index 25cbdc9a98..f8905cfaea 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -30,7 +30,11 @@ namespace ESM esm.getHNOT(mEnabled, "ENAB"); if (mVersion <= MaxOldCountFormatVersion) - esm.getHNOT(mRef.mCount, "COUN"); + { + if (mVersion <= MaxOldGoldValueFormatVersion) + mRef.mCount = std::max(1, mRef.mCount); + esm.getHNOT("COUN", mRef.mCount); + } mPosition = mRef.mPos; esm.getHNOT("POS_", mPosition.pos, mPosition.rot); From db5a43db30fc1d1a05018be032d3aff55c3575df Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 24 Dec 2023 17:48:40 +0000 Subject: [PATCH 136/451] Allow top-level prefix to be found in the middle of a path --- components/misc/resourcehelpers.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 7386dceb9f..c9a3591046 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -75,6 +75,17 @@ std::string Misc::ResourceHelpers::correctResourcePath( needsPrefix = false; break; } + else + { + std::string topLevelPrefix = std::string{ potentialTopLevelDirectory } + '\\'; + size_t topLevelPos = correctedPath.find('\\' + topLevelPrefix); + if (topLevelPos != std::string::npos) + { + correctedPath.erase(0, topLevelPos + 1); + needsPrefix = false; + break; + } + } } if (needsPrefix) correctedPath = std::string{ topLevelDirectories.front() } + '\\' + correctedPath; From 1717e696b16ff852a8e274f0f0be8c1699084376 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 23 Feb 2024 00:06:51 +0000 Subject: [PATCH 137/451] Format before clang notices and sends me an angry email --- components/misc/resourcehelpers.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index c9a3591046..119936f2ab 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -69,7 +69,8 @@ std::string Misc::ResourceHelpers::correctResourcePath( bool needsPrefix = true; for (std::string_view potentialTopLevelDirectory : topLevelDirectories) { - if (correctedPath.starts_with(potentialTopLevelDirectory) && correctedPath.size() > potentialTopLevelDirectory.size() + if (correctedPath.starts_with(potentialTopLevelDirectory) + && correctedPath.size() > potentialTopLevelDirectory.size() && correctedPath[potentialTopLevelDirectory.size()] == '\\') { needsPrefix = false; From 36a75cdb29546f226b87fcda86d4caf683879561 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 14 May 2023 19:05:26 +0100 Subject: [PATCH 138/451] Get the GLExtensions instance when a context is created --- apps/opencs/view/render/scenewidget.cpp | 3 +++ apps/openmw/engine.cpp | 30 ++++++++++++---------- apps/openmw/mwrender/ripples.cpp | 7 ++--- components/CMakeLists.txt | 2 +- components/resource/imagemanager.cpp | 8 +++--- components/sceneutil/depth.cpp | 4 +-- components/sceneutil/glextensions.cpp | 28 ++++++++++++++++++++ components/sceneutil/glextensions.hpp | 20 +++++++++++++++ components/sceneutil/lightmanager.cpp | 7 ++--- components/sceneutil/mwshadowtechnique.cpp | 4 +-- components/shader/shadervisitor.cpp | 4 +-- 11 files changed, 86 insertions(+), 31 deletions(-) create mode 100644 components/sceneutil/glextensions.cpp create mode 100644 components/sceneutil/glextensions.hpp diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 953e3076b3..716a087d02 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include "../widget/scenetoolmode.hpp" @@ -76,6 +77,8 @@ namespace CSVRender = new osgViewer::GraphicsWindowEmbedded(0, 0, width(), height()); mWidget->setGraphicsWindowEmbedded(window); + mRenderer->setRealizeOperation(new SceneUtil::GetGLExtensionsOperation()); + int frameRateLimit = CSMPrefs::get()["Rendering"]["framerate-limit"].toInt(); mRenderer->setRunMaxFrameRate(frameRateLimit); mRenderer->setUseConfigureAffinity(false); diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 75687ff281..2e0e591538 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include @@ -600,6 +601,7 @@ void OMW::Engine::createWindow() mViewer->setRealizeOperation(realizeOperations); osg::ref_ptr identifyOp = new IdentifyOpenGLOperation(); realizeOperations->add(identifyOp); + realizeOperations->add(new SceneUtil::GetGLExtensionsOperation()); if (Debug::shouldDebugOpenGL()) realizeOperations->add(new Debug::EnableGLDebugOperation()); @@ -780,13 +782,13 @@ void OMW::Engine::prepareEngine() // gui needs our shaders path before everything else mResourceSystem->getSceneManager()->setShaderPath(mResDir / "shaders"); - osg::ref_ptr exts = osg::GLExtensions::Get(0, false); - bool shadersSupported = exts && (exts->glslLanguageVersion >= 1.2f); + osg::GLExtensions& exts = SceneUtil::getGLExtensions(); + bool shadersSupported = exts.glslLanguageVersion >= 1.2f; #if OSG_VERSION_LESS_THAN(3, 6, 6) // hack fix for https://github.com/openscenegraph/OpenSceneGraph/issues/1028 - if (exts) - exts->glRenderbufferStorageMultisampleCoverageNV = nullptr; + if (!osg::isGLExtensionSupported(exts.contextID, "NV_framebuffer_multisample_coverage")) + exts.glRenderbufferStorageMultisampleCoverageNV = nullptr; #endif osg::ref_ptr guiRoot = new osg::Group; @@ -844,18 +846,18 @@ void OMW::Engine::prepareEngine() const MWWorld::Store* gmst = &mWorld->getStore().get(); mL10nManager->setGmstLoader( [gmst, misses = std::set>()](std::string_view gmstName) mutable { - const ESM::GameSetting* res = gmst->search(gmstName); - if (res && res->mValue.getType() == ESM::VT_String) - return res->mValue.getString(); - else + const ESM::GameSetting* res = gmst->search(gmstName); + if (res && res->mValue.getType() == ESM::VT_String) + return res->mValue.getString(); + else + { + if (misses.count(gmstName) == 0) { - if (misses.count(gmstName) == 0) - { - misses.emplace(gmstName); - Log(Debug::Error) << "GMST " << gmstName << " not found"; - } - return std::string("GMST:") + std::string(gmstName); + misses.emplace(gmstName); + Log(Debug::Error) << "GMST " << gmstName << " not found"; } + return std::string("GMST:") + std::string(gmstName); + } }); mWindowManager->setStore(mWorld->getStore()); diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index dea372666e..4599c2c946 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "../mwworld/ptr.hpp" @@ -43,9 +44,9 @@ namespace MWRender mUseCompute = false; #else constexpr float minimumGLVersionRequiredForCompute = 4.4; - osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); - mUseCompute = exts->glVersion >= minimumGLVersionRequiredForCompute - && exts->glslLanguageVersion >= minimumGLVersionRequiredForCompute; + osg::GLExtensions& exts = SceneUtil::getGLExtensions(); + mUseCompute = exts.glVersion >= minimumGLVersionRequiredForCompute + && exts.glslLanguageVersion >= minimumGLVersionRequiredForCompute; #endif if (mUseCompute) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index dc195d8d0b..9feec375a2 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -122,7 +122,7 @@ add_component_dir (sceneutil lightmanager lightutil positionattitudetransform workqueue pathgridutil waterutil writescene serialize optimizer detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt screencapture depth color riggeometryosgaextension extradata unrefqueue lightcommon lightingmethod clearcolor - cullsafeboundsvisitor keyframe nodecallback textkeymap + cullsafeboundsvisitor keyframe nodecallback textkeymap glextensions ) add_component_dir (nif diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 26fd60d7ea..124ff9b6ad 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -65,12 +66,11 @@ namespace Resource case (GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): case (GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): { - osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); - if (exts - && !exts->isTextureCompressionS3TCSupported + osg::GLExtensions& exts = SceneUtil::getGLExtensions(); + if (!exts.isTextureCompressionS3TCSupported // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a // patch to OSG. - && !osg::isGLExtensionSupported(0, "GL_S3_s3tc")) + && !osg::isGLExtensionSupported(exts.contextID, "GL_S3_s3tc")) { return false; } diff --git a/components/sceneutil/depth.cpp b/components/sceneutil/depth.cpp index 738fa93dd8..5232d321dc 100644 --- a/components/sceneutil/depth.cpp +++ b/components/sceneutil/depth.cpp @@ -4,6 +4,7 @@ #include #include +#include #include namespace SceneUtil @@ -116,8 +117,7 @@ namespace SceneUtil if (Settings::camera().mReverseZ) { - osg::ref_ptr exts = osg::GLExtensions::Get(0, false); - if (exts && exts->isClipControlSupported) + if (SceneUtil::getGLExtensions().isClipControlSupported) { enableReverseZ = true; Log(Debug::Info) << "Using reverse-z depth buffer"; diff --git a/components/sceneutil/glextensions.cpp b/components/sceneutil/glextensions.cpp new file mode 100644 index 0000000000..078af90c3c --- /dev/null +++ b/components/sceneutil/glextensions.cpp @@ -0,0 +1,28 @@ +#include "glextensions.hpp" + +namespace SceneUtil +{ + namespace + { + osg::observer_ptr sGLExtensions; + } + + osg::GLExtensions& getGLExtensions() + { + if (!sGLExtensions) + throw std::runtime_error( + "GetGLExtensionsOperation was not used when the current context was created or there is no current " + "context"); + return *sGLExtensions; + } + + GetGLExtensionsOperation::GetGLExtensionsOperation() + : GraphicsOperation("GetGLExtensionsOperation", false) + { + } + + void GetGLExtensionsOperation::operator()(osg::GraphicsContext* graphicsContext) + { + sGLExtensions = graphicsContext->getState()->get(); + } +} diff --git a/components/sceneutil/glextensions.hpp b/components/sceneutil/glextensions.hpp new file mode 100644 index 0000000000..17a4eb8488 --- /dev/null +++ b/components/sceneutil/glextensions.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_GLEXTENSIONS_H +#define OPENMW_COMPONENTS_SCENEUTIL_GLEXTENSIONS_H + +#include +#include + +namespace SceneUtil +{ + osg::GLExtensions& getGLExtensions(); + + class GetGLExtensionsOperation : public osg::GraphicsOperation + { + public: + GetGLExtensionsOperation(); + + void operator()(osg::GraphicsContext* graphicsContext) override; + }; +} + +#endif diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 8f7304416b..48efb7fda9 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -824,9 +825,9 @@ namespace SceneUtil , mPointLightFadeEnd(0.f) , mPointLightFadeStart(0.f) { - osg::GLExtensions* exts = osg::GLExtensions::Get(0, false); - bool supportsUBO = exts && exts->isUniformBufferObjectSupported; - bool supportsGPU4 = exts && exts->isGpuShader4Supported; + osg::GLExtensions& exts = SceneUtil::getGLExtensions(); + bool supportsUBO = exts.isUniformBufferObjectSupported; + bool supportsGPU4 = exts.isGpuShader4Supported; mSupported[static_cast(LightingMethod::FFP)] = true; mSupported[static_cast(LightingMethod::PerObjectUniform)] = true; diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index d0c270971a..d1553cc8d8 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -30,6 +30,7 @@ #include #include +#include "glextensions.hpp" #include "shadowsbin.hpp" namespace { @@ -920,8 +921,7 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh // This can't be part of the constructor as OSG mandates that there be a trivial constructor available osg::ref_ptr castingVertexShader = shaderManager.getShader("shadowcasting.vert"); - osg::ref_ptr exts = osg::GLExtensions::Get(0, false); - std::string useGPUShader4 = exts && exts->isGpuShader4Supported ? "1" : "0"; + std::string useGPUShader4 = SceneUtil::getGLExtensions().isGpuShader4Supported ? "1" : "0"; for (int alphaFunc = GL_NEVER; alphaFunc <= GL_ALWAYS; ++alphaFunc) { auto& program = _castingPrograms[alphaFunc - GL_NEVER]; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index e281f64448..7bce9de2a6 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -676,8 +677,7 @@ namespace Shader defineMap["adjustCoverage"] = "1"; // Preventing alpha tested stuff shrinking as lower mip levels are used requires knowing the texture size - osg::ref_ptr exts = osg::GLExtensions::Get(0, false); - if (exts && exts->isGpuShader4Supported) + if (SceneUtil::getGLExtensions().isGpuShader4Supported) defineMap["useGPUShader4"] = "1"; // We could fall back to a texture size uniform if EXT_gpu_shader4 is missing } From 53afa6b1854725973a21b3e15b7f058d4b6b54f6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 14 May 2023 22:06:21 +0100 Subject: [PATCH 139/451] Appease clang-format by changing something I didn't touch --- apps/openmw/engine.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 2e0e591538..49833040d6 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -846,18 +846,18 @@ void OMW::Engine::prepareEngine() const MWWorld::Store* gmst = &mWorld->getStore().get(); mL10nManager->setGmstLoader( [gmst, misses = std::set>()](std::string_view gmstName) mutable { - const ESM::GameSetting* res = gmst->search(gmstName); - if (res && res->mValue.getType() == ESM::VT_String) - return res->mValue.getString(); - else - { - if (misses.count(gmstName) == 0) + const ESM::GameSetting* res = gmst->search(gmstName); + if (res && res->mValue.getType() == ESM::VT_String) + return res->mValue.getString(); + else { - misses.emplace(gmstName); - Log(Debug::Error) << "GMST " << gmstName << " not found"; + if (misses.count(gmstName) == 0) + { + misses.emplace(gmstName); + Log(Debug::Error) << "GMST " << gmstName << " not found"; + } + return std::string("GMST:") + std::string(gmstName); } - return std::string("GMST:") + std::string(gmstName); - } }); mWindowManager->setStore(mWorld->getStore()); From ec4731d454a6eadfb33cefe46c9c0398581443c2 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 14 May 2023 22:38:43 +0100 Subject: [PATCH 140/451] Cope with scene widgets being destroyed in a weird order I can't actually test this as the CS still doesn't get far enough with this MR. --- components/sceneutil/glextensions.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/components/sceneutil/glextensions.cpp b/components/sceneutil/glextensions.cpp index 078af90c3c..eb7783c45f 100644 --- a/components/sceneutil/glextensions.cpp +++ b/components/sceneutil/glextensions.cpp @@ -4,16 +4,29 @@ namespace SceneUtil { namespace { - osg::observer_ptr sGLExtensions; + std::set> sGLExtensions; + + class GLExtensionsObserver : public osg::Observer + { + public: + static GLExtensionsObserver sInstance; + + void objectDeleted(void* referenced) override + { + sGLExtensions.erase(static_cast(referenced)); + } + }; + + GLExtensionsObserver GLExtensionsObserver::sInstance{}; } osg::GLExtensions& getGLExtensions() { - if (!sGLExtensions) + if (sGLExtensions.empty()) throw std::runtime_error( "GetGLExtensionsOperation was not used when the current context was created or there is no current " "context"); - return *sGLExtensions; + return **sGLExtensions.begin(); } GetGLExtensionsOperation::GetGLExtensionsOperation() @@ -23,6 +36,7 @@ namespace SceneUtil void GetGLExtensionsOperation::operator()(osg::GraphicsContext* graphicsContext) { - sGLExtensions = graphicsContext->getState()->get(); + auto [itr, _] = sGLExtensions.emplace(graphicsContext->getState()->get()); + (*itr)->addObserver(&GLExtensionsObserver::sInstance); } } From 2bc091fc05f22b89d6019a7440f179cd1fcca3bb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 14 May 2023 22:51:16 +0100 Subject: [PATCH 141/451] Include missing header I thought I'd seen this class defined in one of the existing headers with a different name, but I was muddling its forward declaration and a different class being in a non-obvious header. --- components/sceneutil/glextensions.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/sceneutil/glextensions.cpp b/components/sceneutil/glextensions.cpp index eb7783c45f..310823a8ea 100644 --- a/components/sceneutil/glextensions.cpp +++ b/components/sceneutil/glextensions.cpp @@ -1,5 +1,7 @@ #include "glextensions.hpp" +#include + namespace SceneUtil { namespace From 6406095bfb478a25c0831c41db7dfc1bd26a480e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 23 Feb 2024 01:34:01 +0000 Subject: [PATCH 142/451] s p a n --- components/misc/resourcehelpers.cpp | 8 ++++---- components/misc/resourcehelpers.hpp | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 119936f2ab..4e7f7c41e3 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -47,7 +47,7 @@ bool Misc::ResourceHelpers::changeExtensionToDds(std::string& path) } std::string Misc::ResourceHelpers::correctResourcePath( - const std::vector& topLevelDirectories, std::string_view resPath, const VFS::Manager* vfs) + std::span topLevelDirectories, std::string_view resPath, const VFS::Manager* vfs) { /* Bethesda at some point converted all their BSA * textures from tga to dds for increased load speed, but all @@ -124,17 +124,17 @@ std::string Misc::ResourceHelpers::correctResourcePath( std::string Misc::ResourceHelpers::correctTexturePath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath({ "textures", "bookart" }, resPath, vfs); + return correctResourcePath({ { "textures", "bookart" } }, resPath, vfs); } std::string Misc::ResourceHelpers::correctIconPath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath({ "icons" }, resPath, vfs); + return correctResourcePath({ { "icons" } }, resPath, vfs); } std::string Misc::ResourceHelpers::correctBookartPath(std::string_view resPath, const VFS::Manager* vfs) { - return correctResourcePath({ "bookart", "textures" }, resPath, vfs); + return correctResourcePath({ { "bookart", "textures" } }, resPath, vfs); } std::string Misc::ResourceHelpers::correctBookartPath( diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index c98840dd61..bd95f2376b 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -1,6 +1,7 @@ #ifndef MISC_RESOURCEHELPERS_H #define MISC_RESOURCEHELPERS_H +#include #include #include #include @@ -23,8 +24,8 @@ namespace Misc namespace ResourceHelpers { bool changeExtensionToDds(std::string& path); - std::string correctResourcePath(const std::vector& topLevelDirectories, - std::string_view resPath, const VFS::Manager* vfs); + std::string correctResourcePath( + std::span topLevelDirectories, std::string_view resPath, const VFS::Manager* vfs); std::string correctTexturePath(std::string_view resPath, const VFS::Manager* vfs); std::string correctIconPath(std::string_view resPath, const VFS::Manager* vfs); std::string correctBookartPath(std::string_view resPath, const VFS::Manager* vfs); From 83ab028bef1b632de03f2cefdc16f2ede426634c Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Fri, 23 Feb 2024 12:37:20 +0000 Subject: [PATCH 143/451] Improve in-game text --- AUTHORS.md | 1 + files/data/l10n/OMWCamera/en.yaml | 34 ++++++++-------- files/data/l10n/OMWControls/en.yaml | 61 ++++++++++++++--------------- files/data/l10n/OMWEngine/en.yaml | 16 ++++---- files/data/l10n/OMWShaders/en.yaml | 10 ++--- 5 files changed, 61 insertions(+), 61 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 4d03fba227..7c06d72287 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -15,6 +15,7 @@ Programmers Nicolay Korslund - Project leader 2008-2010 scrawl - Top contributor + AbduSharif Adam Hogan (aurix) Aesylwinn aegis diff --git a/files/data/l10n/OMWCamera/en.yaml b/files/data/l10n/OMWCamera/en.yaml index d030bd22ec..609b028167 100644 --- a/files/data/l10n/OMWCamera/en.yaml +++ b/files/data/l10n/OMWCamera/en.yaml @@ -1,43 +1,43 @@ Camera: "OpenMW Camera" -settingsPageDescription: "OpenMW Camera settings" +settingsPageDescription: "OpenMW camera settings." -thirdPersonSettings: "Third person mode" +thirdPersonSettings: "Third Person Mode" -viewOverShoulder: "View over the shoulder" +viewOverShoulder: "View Over the Shoulder" viewOverShoulderDescription: | Controls third person view mode. No: view is centered on the character's head. Crosshair is hidden. Yes: while weapon sheathed the camera is positioned behind the character's shoulder, crosshair is always visible. -shoulderOffsetX: "Shoulder view horizontal offset" +shoulderOffsetX: "Shoulder View Horizontal Offset" shoulderOffsetXDescription: > Horizontal offset of the over-the-shoulder view. For the left shoulder use a negative value. -shoulderOffsetY: "Shoulder view vertical offset" +shoulderOffsetY: "Shoulder View Vertical Offset" shoulderOffsetYDescription: > Vertical offset of the over-the-shoulder view. -autoSwitchShoulder: "Auto switch shoulder" +autoSwitchShoulder: "Auto Switch Shoulder" autoSwitchShoulderDescription: > When there are obstacles that would push the camera close to the player character, this setting makes the camera automatically switch to the shoulder farther away from the obstacles. -zoomOutWhenMoveCoef: "Zoom out when move coef" +zoomOutWhenMoveCoef: "Zoom Out When Move Coef" zoomOutWhenMoveCoefDescription: > Moves the camera away (positive value) or towards (negative value) the player character while the character is moving. Works only if "view over the shoulder" is enabled. Set this to zero to disable (default: 20.0). -previewIfStandStill: "Preview if stand still" +previewIfStandStill: "Preview if Stand Still" previewIfStandStillDescription: > Prevents the player character from turning towards the camera direction while they're idle and have their weapon sheathed. -deferredPreviewRotation: "Deferred preview rotation" +deferredPreviewRotation: "Deferred Preview Rotation" deferredPreviewRotationDescription: | If enabled then the character smoothly rotates to the view direction after exiting preview or vanity mode. If disabled then the camera rotates rather than the character. -ignoreNC: "Ignore 'No Collision' flag" +ignoreNC: "Ignore 'No Collision' Flag" ignoreNCDescription: > Prevents the camera from clipping through objects that have NC (No Collision) flag turned on in the NIF model. @@ -46,27 +46,27 @@ move360Description: > Makes the movement direction independent from the camera direction while the player character's weapon is sheathed. For example, the player character will look at the camera while running backwards. -move360TurnSpeed: "Move 360 turning speed" +move360TurnSpeed: "Move 360 Turning Speed" move360TurnSpeedDescription: "Turning speed multiplier (default: 5.0)." -slowViewChange: "Smooth view change" +slowViewChange: "Smooth View Change" slowViewChangeDescription: "Makes the transition from 1st person to 3rd person view non-instantaneous." -povAutoSwitch: "First person auto switch" +povAutoSwitch: "First Person Auto Switch" povAutoSwitchDescription: "Auto switch to the first person view if there is an obstacle right behind the player." -headBobbingSettings: "Head bobbing in first person view" +headBobbingSettings: "Head Bobbing in First Person View" headBobbing_enabled: "Enabled" headBobbing_enabledDescription: "" -headBobbing_step: "Base step length" +headBobbing_step: "Base Step Length" headBobbing_stepDescription: "The length of each step (default: 90.0)." -headBobbing_height: "Step height" +headBobbing_height: "Step Height" headBobbing_heightDescription: "The amplitude of the head bobbing (default: 3.0)." -headBobbing_roll: "Max roll angle" +headBobbing_roll: "Max Roll Angle" headBobbing_rollDescription: "The maximum roll angle in degrees (default: 0.2)." diff --git a/files/data/l10n/OMWControls/en.yaml b/files/data/l10n/OMWControls/en.yaml index 9c45c1d1e5..c034eb8683 100644 --- a/files/data/l10n/OMWControls/en.yaml +++ b/files/data/l10n/OMWControls/en.yaml @@ -1,22 +1,21 @@ ControlsPage: "OpenMW Controls" -ControlsPageDescription: "Additional settings related to player controls" +ControlsPageDescription: "Additional settings related to player controls." MovementSettings: "Movement" -alwaysRun: "Always run" +alwaysRun: "Always Run" alwaysRunDescription: | - If this setting is true, the character is running by default, otherwise the character is walking by default. - The shift key will temporarily invert this setting, and the caps lock key will invert this setting while it's "locked". + If this setting is true, the character will run by default, otherwise the character will walk by default. + The Shift key will temporarily invert this setting, and the Caps Lock key will invert this setting while it's "locked". -toggleSneak: "Toggle sneak" +toggleSneak: "Toggle Sneak" toggleSneakDescription: | - This setting causes the sneak key (bound to Ctrl by default) to toggle sneaking on and off - rather than requiring the key to be held down while sneaking. + This setting makes the Sneak key (bound to Ctrl by default) toggle sneaking instead of having to be held down to sneak. Players that spend significant time sneaking may find the character easier to control with this option enabled. -smoothControllerMovement: "Smooth controller movement" +smoothControllerMovement: "Smooth Controller Movement" smoothControllerMovementDescription: | - Enables smooth movement with controller stick, with no abrupt switch from walking to running. + Enables smooth controller stick movement. This makes the transition from walking to running less abrupt. TogglePOV_name: "Toggle POV" TogglePOV_description: "Toggle between first and third person view. Hold to enter preview mode." @@ -25,61 +24,61 @@ Zoom3rdPerson_name: "Zoom In/Out" Zoom3rdPerson_description: "Moves the camera closer / further away when in third person view." MoveForward_name: "Move Forward" -MoveForward_description: "Can cancel out with Move Backward" +MoveForward_description: "Can cancel out with Move Backward." MoveBackward_name: "Move Backward" -MoveBackward_description: "Can cancel out with Move Forward" +MoveBackward_description: "Can cancel out with Move Forward." MoveLeft_name: "Move Left" -MoveLeft_description: "Can cancel out with Move Right" +MoveLeft_description: "Can cancel out with Move Right." MoveRight_name: "Move Right" -MoveRight_description: "Can cancel out with Move Left" +MoveRight_description: "Can cancel out with Move Left." Use_name: "Use" -Use_description: "Attack with a weapon or cast a spell depending on current stance" +Use_description: "Attack with a weapon or cast a spell depending on the current stance." Run_name: "Run" -Run_description: "Hold to run/walk, depending on the Always Run setting" +Run_description: "Hold to run/walk depending on the Always Run setting." AlwaysRun_name: "Always Run" -AlwaysRun_description: "Toggle the Always Run setting" +AlwaysRun_description: "Toggle the Always Run setting." Jump_name: "Jump" -Jump_description: "Jump whenever you are on the ground" +Jump_description: "Jump whenever you are on the ground." AutoMove_name: "Auto Run" -AutoMove_description: "Toggle continous forward movement" +AutoMove_description: "Toggle continuous forward movement." Sneak_name: "Sneak" -Sneak_description: "Hold to sneak, if the Toggle Sneak setting is off" +Sneak_description: "Hold to sneak if the Toggle Sneak setting is off." ToggleSneak_name: "Toggle Sneak" -ToggleSneak_description: "Toggle sneak, if the Toggle Sneak setting is on" +ToggleSneak_description: "Toggle sneak if the Toggle Sneak setting is on." ToggleWeapon_name: "Ready Weapon" -ToggleWeapon_description: "Enter or leave the weapon stance" +ToggleWeapon_description: "Enter or leave the weapon stance." ToggleSpell_name: "Ready Magic" -ToggleSpell_description: "Enter or leave the magic stance" +ToggleSpell_description: "Enter or leave the magic stance." Inventory_name: "Inventory" -Inventory_description: "Open the inventory" +Inventory_description: "Open the inventory." Journal_name: "Journal" -Journal_description: "Open the journal" +Journal_description: "Open the journal." -QuickKeysMenu_name: "QuickKeysMenu" -QuickKeysMenu_description: "Open the quick keys menu" +QuickKeysMenu_name: "Quick Menu" +QuickKeysMenu_description: "Open the quick keys menu." SmoothMoveForward_name: "Smooth Move Forward" -SmoothMoveForward_description: "Forward movement adjusted for smooth Walk-Run transitions" +SmoothMoveForward_description: "Forward movement adjusted for smooth Walk-Run transitions." SmoothMoveBackward_name: "Smooth Move Backward" -SmoothMoveBackward_description: "Backward movement adjusted for smooth Walk-Run transitions" +SmoothMoveBackward_description: "Backward movement adjusted for smooth Walk-Run transitions." SmoothMoveLeft_name: "Smooth Move Left" -SmoothMoveLeft_description: "Left movement adjusted for smooth Walk-Run transitions" +SmoothMoveLeft_description: "Left movement adjusted for smooth Walk-Run transitions." -SkmoothMoveRight_name: "SmoothMove Right" -SkmoothMoveRight_description: "Right movement adjusted for smooth Walk-Run transitions" +SkmoothMoveRight_name: "Smooth Move Right" +SkmoothMoveRight_description: "Right movement adjusted for smooth Walk-Run transitions." diff --git a/files/data/l10n/OMWEngine/en.yaml b/files/data/l10n/OMWEngine/en.yaml index f6ad237394..55ebbf3e94 100644 --- a/files/data/l10n/OMWEngine/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -13,7 +13,7 @@ PhysicsProfiler: "Physics Profiler" # Messages AskLoadLastSave: "The most recent save is '%s'. Do you want to load it?" -BuildingNavigationMesh: "Building navigation mesh" +BuildingNavigationMesh: "Building Navigation Mesh" InitializingData: "Initializing Data..." LoadingExterior: "Loading Area" LoadingFailed: "Failed to load saved game" @@ -57,7 +57,7 @@ MissingContentFilesListCopy: |- } OverwriteGameConfirmation: "Are you sure you want to overwrite this saved game?" SelectCharacter: "Select Character..." -TimePlayed: "Time played" +TimePlayed: "Time Played" # Settings menu @@ -131,12 +131,12 @@ PrimaryLanguageTooltip: "Localization files for this language have the highest p QualityHigh: "High" QualityLow: "Low" QualityMedium: "Medium" -RainRippleDetail: "Rain ripple detail" +RainRippleDetail: "Rain Ripple Detail" RainRippleDetailDense: "Dense" RainRippleDetailSimple: "Simple" RainRippleDetailSparse: "Sparse" RebindAction: "Press a key or button to rebind this control." -ReflectionShaderDetail: "Reflection shader detail" +ReflectionShaderDetail: "Reflection Shader Detail" ReflectionShaderDetailActors: "Actors" ReflectionShaderDetailGroundcover: "Groundcover" ReflectionShaderDetailObjects: "Objects" @@ -154,8 +154,8 @@ SensitivityHigh: "High" SensitivityLow: "Low" SettingsWindow: "Options" Subtitles: "Subtitles" -TestingExteriorCells: "Testing exterior cells" -TestingInteriorCells: "Testing interior cells" +TestingExteriorCells: "Testing Exterior Cells" +TestingInteriorCells: "Testing Interior Cells" TextureFiltering: "Texture Filtering" TextureFilteringBilinear: "Bilinear" TextureFilteringDisabled: "None" @@ -170,8 +170,8 @@ ViewDistance: "View Distance" VSync: "VSync" VSyncAdaptive: "Adaptive" Water: "Water" -WaterShader: "Water shader" -WaterShaderTextureQuality: "Texture quality" +WaterShader: "Water Shader" +WaterShaderTextureQuality: "Texture Quality" WindowBorder: "Window Border" WindowMode: "Window Mode" WindowModeFullscreen: "Fullscreen" diff --git a/files/data/l10n/OMWShaders/en.yaml b/files/data/l10n/OMWShaders/en.yaml index 6588591f00..a8c13da34b 100644 --- a/files/data/l10n/OMWShaders/en.yaml +++ b/files/data/l10n/OMWShaders/en.yaml @@ -14,7 +14,7 @@ KeyboardControls: | Shift+Left-Arrow > Deactive shader Shift+Up-Arrow > Move shader up Shift+Down-Arrow > Move shader down -PostProcessHUD: "Postprocess HUD" +PostProcessHUD: "Post Processor HUD" ResetShader: "Reset shader to default state" ShaderLocked: "Locked" ShaderLockedDescription: "Cannot be toggled or moved, controlled by external Lua script" @@ -30,11 +30,11 @@ BloomDescription: "Bloom shader performing its calculations in (approximately) l DebugDescription: "Debug shader." DebugHeaderDepth: "Depth Buffer" DebugHeaderNormals: "Normals" -DisplayDepthFactorName: "Depth colour factor" +DisplayDepthFactorName: "Depth Colour Factor" DisplayDepthFactorDescription: "Determines correlation between pixel depth value and its output colour. High values lead to brighter image." -DisplayDepthName: "Visualize depth buffer" -DisplayNormalsName: "Visualize pass normals" -NormalsInWorldSpace: "Show normals in world space" +DisplayDepthName: "Visualize Depth Buffer" +DisplayNormalsName: "Visualize Pass Normals" +NormalsInWorldSpace: "Show Normals in World Space" ContrastLevelDescription: "Constrast level." ContrastLevelName: "Constrast" GammaLevelDescription: "Gamma level." From 3fbd97ffc876cf776779db3e3b4cde6984b1cacb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 23 Feb 2024 12:48:39 +0000 Subject: [PATCH 144/451] Remove unused header --- components/misc/resourcehelpers.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index bd95f2376b..a4d46f2611 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -4,7 +4,6 @@ #include #include #include -#include namespace VFS { From fc1f2446278516e7936663c470addf5c5abd076c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 23 Feb 2024 17:01:59 +0400 Subject: [PATCH 145/451] Add missing initialization --- components/debug/debugdraw.cpp | 2 -- components/debug/debugdraw.hpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index e7aa7d8cce..2bc7358259 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -316,8 +316,6 @@ Debug::DebugDrawer::DebugDrawer(const DebugDrawer& copy, const osg::CopyOp& copy Debug::DebugDrawer::DebugDrawer(Shader::ShaderManager& shaderManager) { - mCurrentFrame = 0; - auto program = shaderManager.getProgram("debug"); setCullingActive(false); diff --git a/components/debug/debugdraw.hpp b/components/debug/debugdraw.hpp index 2518813cad..eb4219e06b 100644 --- a/components/debug/debugdraw.hpp +++ b/components/debug/debugdraw.hpp @@ -101,7 +101,7 @@ namespace Debug void addLine(const osg::Vec3& start, const osg::Vec3& end, const osg::Vec3 color = colorWhite); private: - unsigned int mCurrentFrame; + unsigned int mCurrentFrame = 0; std::array, 2> mCustomDebugDrawer; }; From 1126f38a1e7c6b08cf8777ffdcad0c29c0023653 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 23 Feb 2024 17:02:40 +0400 Subject: [PATCH 146/451] Do not copy the whole attributes store --- apps/openmw/mwlua/stats.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index c6492c1ec2..eaa1f89d97 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -476,7 +476,7 @@ namespace MWLua auto skillIncreasesForAttributeStatsT = context.mLua->sol().new_usertype("SkillIncreasesForAttributeStats"); - for (auto attribute : MWBase::Environment::get().getESMStore()->get()) + for (const auto& attribute : MWBase::Environment::get().getESMStore()->get()) { skillIncreasesForAttributeStatsT[ESM::RefId(attribute.mId).serializeText()] = sol::property( [=](const SkillIncreasesForAttributeStats& stat) { return stat.get(context, attribute.mId); }, From cf6b6020a013f279a42bc4e63bbb1d6393e4adec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 23 Feb 2024 17:03:13 +0400 Subject: [PATCH 147/451] Move local variables --- apps/openmw/mwlua/nearbybindings.cpp | 4 ++-- components/lua/asyncpackage.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 7eda965e96..af6980fb7f 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -163,8 +163,8 @@ namespace MWLua ignore = parseIgnoreList(*options); } - context.mLuaManager->addAction([context, ignore, callback = LuaUtil::Callback::fromLua(callback), from, - to] { + context.mLuaManager->addAction([context, ignore = std::move(ignore), + callback = LuaUtil::Callback::fromLua(callback), from, to] { MWPhysics::RayCastingResult res; MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false, ignore); context.mLuaManager->queueCallback(callback, sol::main_object(context.mLua->sol(), sol::in_place, res)); diff --git a/components/lua/asyncpackage.cpp b/components/lua/asyncpackage.cpp index 6e13406511..5d563e6276 100644 --- a/components/lua/asyncpackage.cpp +++ b/components/lua/asyncpackage.cpp @@ -94,7 +94,7 @@ namespace LuaUtil sol::table callbackMeta = Callback::makeMetatable(L); api["callback"] = [callbackMeta](const AsyncPackageId& asyncId, sol::main_protected_function fn) -> sol::table { - return Callback::make(asyncId, fn, callbackMeta); + return Callback::make(asyncId, std::move(fn), callbackMeta); }; auto initializer = [](sol::table hiddenData) { From dec8d32b3a7cf12666157082e7600778756cc67b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 24 Feb 2024 00:54:40 +0000 Subject: [PATCH 148/451] FIx static destruction order chaos --- components/sceneutil/glextensions.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/sceneutil/glextensions.cpp b/components/sceneutil/glextensions.cpp index 310823a8ea..eb55e17b1c 100644 --- a/components/sceneutil/glextensions.cpp +++ b/components/sceneutil/glextensions.cpp @@ -13,12 +13,23 @@ namespace SceneUtil public: static GLExtensionsObserver sInstance; + ~GLExtensionsObserver() override + { + for (auto& ptr : sGLExtensions) + { + osg::ref_ptr ref; + if (ptr.lock(ref)) + ref->removeObserver(this); + } + } + void objectDeleted(void* referenced) override { sGLExtensions.erase(static_cast(referenced)); } }; + // construct after sGLExtensions so this gets destroyed first. GLExtensionsObserver GLExtensionsObserver::sInstance{}; } From 92d57d6e46d8d661969e71be5b39ff36113c9141 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 22 Feb 2024 23:59:23 +0100 Subject: [PATCH 149/451] Make Normalized constructor from const char* explicit --- apps/openmw_test_suite/testing_util.hpp | 7 +++++++ components/vfs/manager.cpp | 5 +++++ components/vfs/manager.hpp | 2 ++ components/vfs/pathutil.hpp | 2 +- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/testing_util.hpp b/apps/openmw_test_suite/testing_util.hpp index ad1b0423ef..60367ffbe9 100644 --- a/apps/openmw_test_suite/testing_util.hpp +++ b/apps/openmw_test_suite/testing_util.hpp @@ -2,6 +2,7 @@ #define TESTING_UTIL_H #include +#include #include #include @@ -73,6 +74,12 @@ namespace TestingOpenMW return vfs; } + inline std::unique_ptr createTestVFS( + std::initializer_list> files) + { + return createTestVFS(VFS::FileMap(files.begin(), files.end())); + } + #define EXPECT_ERROR(X, ERR_SUBSTR) \ try \ { \ diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index a6add0861a..ef5dd495c9 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -57,6 +57,11 @@ namespace VFS return mIndex.find(name) != mIndex.end(); } + bool Manager::exists(Path::NormalizedView name) const + { + return mIndex.find(name) != mIndex.end(); + } + std::string Manager::getArchive(const Path::Normalized& name) const { for (auto it = mArchives.rbegin(); it != mArchives.rend(); ++it) diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 7598b77e68..955538627f 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -43,6 +43,8 @@ namespace VFS /// @note May be called from any thread once the index has been built. bool exists(const Path::Normalized& name) const; + bool exists(Path::NormalizedView name) const; + /// Retrieve a file by name. /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index aa7cad8524..45355cd129 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -122,7 +122,7 @@ namespace VFS::Path { } - Normalized(const char* value) + explicit Normalized(const char* value) : Normalized(std::string_view(value)) { } From ec9c82902189c0189a5532f089e69ac99d96074a Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 22 Feb 2024 23:44:12 +0100 Subject: [PATCH 150/451] Use normalized path for correctSoundPath --- apps/openmw/mwbase/soundmanager.hpp | 6 +- apps/openmw/mwdialogue/dialoguemanagerimp.cpp | 2 +- apps/openmw/mwgui/charactercreation.cpp | 2 +- apps/openmw/mwlua/soundbindings.cpp | 6 +- apps/openmw/mwscript/soundextensions.cpp | 2 +- apps/openmw/mwsound/openal_output.cpp | 4 +- apps/openmw/mwsound/openal_output.hpp | 4 +- apps/openmw/mwsound/sound_buffer.cpp | 5 +- apps/openmw/mwsound/sound_buffer.hpp | 4 +- apps/openmw/mwsound/sound_output.hpp | 3 +- apps/openmw/mwsound/soundmanagerimp.cpp | 8 +-- apps/openmw/mwsound/soundmanagerimp.hpp | 6 +- .../misc/test_resourcehelpers.cpp | 13 +---- apps/openmw_test_suite/testing_util.hpp | 2 +- apps/openmw_test_suite/vfs/testpathutil.cpp | 55 +++++++++++++++++++ components/misc/resourcehelpers.cpp | 17 +++--- components/misc/resourcehelpers.hpp | 6 +- components/vfs/pathutil.hpp | 45 ++++++++++++++- 18 files changed, 143 insertions(+), 47 deletions(-) diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 1f0337869b..05b925f87d 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -6,6 +6,8 @@ #include #include +#include + #include "../mwsound/type.hpp" #include "../mwworld/ptr.hpp" @@ -129,11 +131,11 @@ namespace MWBase /// \param name of the folder that contains the playlist /// Title music playlist is predefined - virtual void say(const MWWorld::ConstPtr& reference, const std::string& filename) = 0; + virtual void say(const MWWorld::ConstPtr& reference, VFS::Path::NormalizedView filename) = 0; ///< Make an actor say some text. /// \param filename name of a sound file in the VFS - virtual void say(const std::string& filename) = 0; + virtual void say(VFS::Path::NormalizedView filename) = 0; ///< Say some text, without an actor ref /// \param filename name of a sound file in the VFS diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 3b0ba47250..556b5b53d7 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -653,7 +653,7 @@ namespace MWDialogue if (Settings::gui().mSubtitles) winMgr->messageBox(info->mResponse); if (!info->mSound.empty()) - sndMgr->say(actor, Misc::ResourceHelpers::correctSoundPath(info->mSound)); + sndMgr->say(actor, Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(info->mSound))); if (!info->mResultScript.empty()) executeScript(info->mResultScript, actor); } diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index c5280d1615..be2d22ae84 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -39,7 +39,7 @@ namespace { const std::string mText; const Response mResponses[3]; - const std::string mSound; + const VFS::Path::Normalized mSound; }; Step sGenerateClassSteps(int number) diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index e8b7089eb8..ad4a498153 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -174,12 +174,12 @@ namespace MWLua api["say"] = sol::overload( [luaManager = context.mLuaManager]( std::string_view fileName, const Object& object, sol::optional text) { - MWBase::Environment::get().getSoundManager()->say(object.ptr(), std::string(fileName)); + MWBase::Environment::get().getSoundManager()->say(object.ptr(), VFS::Path::Normalized(fileName)); if (text) luaManager->addUIMessage(*text); }, [luaManager = context.mLuaManager](std::string_view fileName, sol::optional text) { - MWBase::Environment::get().getSoundManager()->say(std::string(fileName)); + MWBase::Environment::get().getSoundManager()->say(VFS::Path::Normalized(fileName)); if (text) luaManager->addUIMessage(*text); }); @@ -227,7 +227,7 @@ namespace MWLua soundT["maxRange"] = sol::readonly_property([](const ESM::Sound& rec) -> unsigned char { return rec.mData.mMaxRange; }); soundT["fileName"] = sol::readonly_property([](const ESM::Sound& rec) -> std::string { - return VFS::Path::normalizeFilename(Misc::ResourceHelpers::correctSoundPath(rec.mSound)); + return Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(rec.mSound)).value(); }); return LuaUtil::makeReadOnly(api); diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 44cdc25064..ee39860584 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -33,7 +33,7 @@ namespace MWScript MWScript::InterpreterContext& context = static_cast(runtime.getContext()); - std::string file{ runtime.getStringLiteral(runtime[0].mInteger) }; + VFS::Path::Normalized file{ runtime.getStringLiteral(runtime[0].mInteger) }; runtime.pop(); std::string_view text = runtime.getStringLiteral(runtime[0].mInteger); diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 99003d5ce3..0261649fa9 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -1034,7 +1034,7 @@ namespace MWSound return ret; } - std::pair OpenAL_Output::loadSound(const std::string& fname) + std::pair OpenAL_Output::loadSound(VFS::Path::NormalizedView fname) { getALError(); @@ -1045,7 +1045,7 @@ namespace MWSound try { DecoderPtr decoder = mManager.getDecoder(); - decoder->open(Misc::ResourceHelpers::correctSoundPath(fname, decoder->mResourceMgr)); + decoder->open(Misc::ResourceHelpers::correctSoundPath(fname, *decoder->mResourceMgr)); ChannelConfig chans; SampleType type; diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 7636f7bda9..b419038eab 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -7,6 +7,8 @@ #include #include +#include + #include "al.h" #include "alc.h" #include "alext.h" @@ -85,7 +87,7 @@ namespace MWSound std::vector enumerateHrtf() override; - std::pair loadSound(const std::string& fname) override; + std::pair loadSound(VFS::Path::NormalizedView fname) override; size_t unloadSound(Sound_Handle data) override; bool playSound(Sound* sound, Sound_Handle data, float offset) override; diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp index a3fdcb8b5c..f28b268df2 100644 --- a/apps/openmw/mwsound/sound_buffer.cpp +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -183,9 +183,8 @@ namespace MWSound min = std::max(min, 1.0f); max = std::max(min, max); - Sound_Buffer& sfx - = mSoundBuffers.emplace_back(Misc::ResourceHelpers::correctSoundPath(sound.mSound), volume, min, max); - VFS::Path::normalizeFilenameInPlace(sfx.mResourceName); + Sound_Buffer& sfx = mSoundBuffers.emplace_back( + Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normalized(sound.mSound)), volume, min, max); mBufferNameMap.emplace(soundId, &sfx); return &sfx; diff --git a/apps/openmw/mwsound/sound_buffer.hpp b/apps/openmw/mwsound/sound_buffer.hpp index 3bf734a4b6..7de6dab9ae 100644 --- a/apps/openmw/mwsound/sound_buffer.hpp +++ b/apps/openmw/mwsound/sound_buffer.hpp @@ -35,7 +35,7 @@ namespace MWSound { } - const std::string& getResourceName() const noexcept { return mResourceName; } + const VFS::Path::Normalized& getResourceName() const noexcept { return mResourceName; } Sound_Handle getHandle() const noexcept { return mHandle; } @@ -46,7 +46,7 @@ namespace MWSound float getMaxDist() const noexcept { return mMaxDist; } private: - std::string mResourceName; + VFS::Path::Normalized mResourceName; float mVolume; float mMinDist; float mMaxDist; diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index df95f0909e..5a77124985 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -6,6 +6,7 @@ #include #include +#include #include "../mwbase/soundmanager.hpp" @@ -39,7 +40,7 @@ namespace MWSound virtual std::vector enumerateHrtf() = 0; - virtual std::pair loadSound(const std::string& fname) = 0; + virtual std::pair loadSound(VFS::Path::NormalizedView fname) = 0; virtual size_t unloadSound(Sound_Handle data) = 0; virtual bool playSound(Sound* sound, Sound_Handle data, float offset) = 0; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 0cc276807f..3658be4819 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -172,12 +172,12 @@ namespace MWSound return std::make_shared(mVFS); } - DecoderPtr SoundManager::loadVoice(const std::string& voicefile) + DecoderPtr SoundManager::loadVoice(VFS::Path::NormalizedView voicefile) { try { DecoderPtr decoder = getDecoder(); - decoder->open(Misc::ResourceHelpers::correctSoundPath(voicefile, decoder->mResourceMgr)); + decoder->open(Misc::ResourceHelpers::correctSoundPath(voicefile, *decoder->mResourceMgr)); return decoder; } catch (std::exception& e) @@ -380,7 +380,7 @@ namespace MWSound startRandomTitle(); } - void SoundManager::say(const MWWorld::ConstPtr& ptr, const std::string& filename) + void SoundManager::say(const MWWorld::ConstPtr& ptr, VFS::Path::NormalizedView filename) { if (!mOutput->isInitialized()) return; @@ -412,7 +412,7 @@ namespace MWSound return 0.0f; } - void SoundManager::say(const std::string& filename) + void SoundManager::say(VFS::Path::NormalizedView filename) { if (!mOutput->isInitialized()) return; diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 6154d202cd..75b1193118 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -116,7 +116,7 @@ namespace MWSound Sound_Buffer* insertSound(const std::string& soundId, const ESM::Sound* sound); // returns a decoder to start streaming, or nullptr if the sound was not found - DecoderPtr loadVoice(const std::string& voicefile); + DecoderPtr loadVoice(VFS::Path::NormalizedView voicefile); SoundPtr getSoundRef(); StreamPtr getStreamRef(); @@ -188,11 +188,11 @@ namespace MWSound /// \param name of the folder that contains the playlist /// Title music playlist is predefined - void say(const MWWorld::ConstPtr& reference, const std::string& filename) override; + void say(const MWWorld::ConstPtr& reference, VFS::Path::NormalizedView filename) override; ///< Make an actor say some text. /// \param filename name of a sound file in the VFS - void say(const std::string& filename) override; + void say(VFS::Path::NormalizedView filename) override; ///< Say some text, without an actor ref /// \param filename name of a sound file in the VFS diff --git a/apps/openmw_test_suite/misc/test_resourcehelpers.cpp b/apps/openmw_test_suite/misc/test_resourcehelpers.cpp index 0db147d8a3..5290630394 100644 --- a/apps/openmw_test_suite/misc/test_resourcehelpers.cpp +++ b/apps/openmw_test_suite/misc/test_resourcehelpers.cpp @@ -8,26 +8,19 @@ namespace TEST(CorrectSoundPath, wav_files_not_overridden_with_mp3_in_vfs_are_not_corrected) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { "sound/bar.wav", nullptr } }); - EXPECT_EQ(correctSoundPath("sound/bar.wav", mVFS.get()), "sound/bar.wav"); + EXPECT_EQ(correctSoundPath("sound/bar.wav", *mVFS), "sound/bar.wav"); } TEST(CorrectSoundPath, wav_files_overridden_with_mp3_in_vfs_are_corrected) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { "sound/foo.mp3", nullptr } }); - EXPECT_EQ(correctSoundPath("sound/foo.wav", mVFS.get()), "sound/foo.mp3"); + EXPECT_EQ(correctSoundPath("sound/foo.wav", *mVFS), "sound/foo.mp3"); } TEST(CorrectSoundPath, corrected_path_does_not_check_existence_in_vfs) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctSoundPath("sound/foo.wav", mVFS.get()), "sound/foo.mp3"); - } - - TEST(CorrectSoundPath, correct_path_normalize_paths) - { - std::unique_ptr mVFS = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctSoundPath("sound\\foo.wav", mVFS.get()), "sound/foo.mp3"); - EXPECT_EQ(correctSoundPath("SOUND\\foo.WAV", mVFS.get()), "sound/foo.mp3"); + EXPECT_EQ(correctSoundPath("sound/foo.wav", *mVFS), "sound/foo.mp3"); } namespace diff --git a/apps/openmw_test_suite/testing_util.hpp b/apps/openmw_test_suite/testing_util.hpp index 60367ffbe9..0afd04e639 100644 --- a/apps/openmw_test_suite/testing_util.hpp +++ b/apps/openmw_test_suite/testing_util.hpp @@ -75,7 +75,7 @@ namespace TestingOpenMW } inline std::unique_ptr createTestVFS( - std::initializer_list> files) + std::initializer_list> files) { return createTestVFS(VFS::FileMap(files.begin(), files.end())); } diff --git a/apps/openmw_test_suite/vfs/testpathutil.cpp b/apps/openmw_test_suite/vfs/testpathutil.cpp index 23a4d46d12..7b9c9abfb5 100644 --- a/apps/openmw_test_suite/vfs/testpathutil.cpp +++ b/apps/openmw_test_suite/vfs/testpathutil.cpp @@ -65,6 +65,53 @@ namespace VFS::Path EXPECT_EQ(stream.str(), "foo/bar/baz"); } + TEST(NormalizedTest, shouldSupportOperatorDivEqual) + { + Normalized value("foo/bar"); + value /= NormalizedView("baz"); + EXPECT_EQ(value.value(), "foo/bar/baz"); + } + + TEST(NormalizedTest, changeExtensionShouldReplaceAfterLastDot) + { + Normalized value("foo/bar.a"); + ASSERT_TRUE(value.changeExtension("so")); + EXPECT_EQ(value.value(), "foo/bar.so"); + } + + TEST(NormalizedTest, changeExtensionShouldNormalizeExtension) + { + Normalized value("foo/bar.a"); + ASSERT_TRUE(value.changeExtension("SO")); + EXPECT_EQ(value.value(), "foo/bar.so"); + } + + TEST(NormalizedTest, changeExtensionShouldIgnorePathWithoutADot) + { + Normalized value("foo/bar"); + ASSERT_FALSE(value.changeExtension("so")); + EXPECT_EQ(value.value(), "foo/bar"); + } + + TEST(NormalizedTest, changeExtensionShouldIgnorePathWithDotBeforeSeparator) + { + Normalized value("foo.bar/baz"); + ASSERT_FALSE(value.changeExtension("so")); + EXPECT_EQ(value.value(), "foo.bar/baz"); + } + + TEST(NormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithDot) + { + Normalized value("foo.a"); + EXPECT_THROW(value.changeExtension(".so"), std::invalid_argument); + } + + TEST(NormalizedTest, changeExtensionShouldThrowExceptionOnExtensionWithSeparator) + { + Normalized value("foo.a"); + EXPECT_THROW(value.changeExtension("/so"), std::invalid_argument); + } + template struct NormalizedOperatorsTest : Test { @@ -135,5 +182,13 @@ namespace VFS::Path { EXPECT_THROW([] { NormalizedView("Foo\\Bar/baz"); }(), std::invalid_argument); } + + TEST(NormalizedView, shouldSupportOperatorDiv) + { + const NormalizedView a("foo/bar"); + const NormalizedView b("baz"); + const Normalized result = a / b; + EXPECT_EQ(result.value(), "foo/bar/baz"); + } } } diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index ab6aa7907c..1d5b57bfd9 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -180,9 +180,10 @@ std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath) return res; } -std::string Misc::ResourceHelpers::correctSoundPath(const std::string& resPath) +VFS::Path::Normalized Misc::ResourceHelpers::correctSoundPath(VFS::Path::NormalizedView resPath) { - return "sound\\" + resPath; + static constexpr VFS::Path::NormalizedView prefix("sound"); + return prefix / resPath; } std::string Misc::ResourceHelpers::correctMusicPath(const std::string& resPath) @@ -201,17 +202,17 @@ std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath return resPath.substr(prefix.size() + 1); } -std::string Misc::ResourceHelpers::correctSoundPath(std::string_view resPath, const VFS::Manager* vfs) +VFS::Path::Normalized Misc::ResourceHelpers::correctSoundPath( + VFS::Path::NormalizedView resPath, const VFS::Manager& vfs) { // Workaround: Bethesda at some point converted some of the files to mp3, but the references were kept as .wav. - if (!vfs->exists(resPath)) + if (!vfs.exists(resPath)) { - std::string sound{ resPath }; - changeExtension(sound, ".mp3"); - VFS::Path::normalizeFilenameInPlace(sound); + VFS::Path::Normalized sound(resPath); + sound.changeExtension("mp3"); return sound; } - return VFS::Path::normalizeFilename(resPath); + return VFS::Path::Normalized(resPath); } bool Misc::ResourceHelpers::isHiddenMarker(const ESM::RefId& id) diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index e79dae0887..cda99d928d 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -1,6 +1,8 @@ #ifndef MISC_RESOURCEHELPERS_H #define MISC_RESOURCEHELPERS_H +#include + #include #include #include @@ -37,7 +39,7 @@ namespace Misc std::string correctMeshPath(std::string_view resPath); // Adds "sound\\". - std::string correctSoundPath(const std::string& resPath); + VFS::Path::Normalized correctSoundPath(VFS::Path::NormalizedView resPath); // Adds "music\\". std::string correctMusicPath(const std::string& resPath); @@ -45,7 +47,7 @@ namespace Misc // Removes "meshes\\". std::string_view meshPathForESM3(std::string_view resPath); - std::string correctSoundPath(std::string_view resPath, const VFS::Manager* vfs); + VFS::Path::Normalized correctSoundPath(VFS::Path::NormalizedView resPath, const VFS::Manager& vfs); /// marker objects that have a hardcoded function in the game logic, should be hidden from the player bool isHiddenMarker(const ESM::RefId& id); diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 45355cd129..5c5746cf6f 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -11,9 +11,12 @@ namespace VFS::Path { + inline constexpr char separator = '/'; + inline constexpr char extensionSeparator = '.'; + inline constexpr char normalize(char c) { - return c == '\\' ? '/' : Misc::StringUtils::toLower(c); + return c == '\\' ? separator : Misc::StringUtils::toLower(c); } inline constexpr bool isNormalized(std::string_view name) @@ -21,9 +24,14 @@ namespace VFS::Path return std::all_of(name.begin(), name.end(), [](char v) { return v == normalize(v); }); } + inline void normalizeFilenameInPlace(auto begin, auto end) + { + std::transform(begin, end, begin, normalize); + } + inline void normalizeFilenameInPlace(std::string& name) { - std::transform(name.begin(), name.end(), name.begin(), normalize); + normalizeFilenameInPlace(name.begin(), name.end()); } /// Normalize the given filename, making slashes/backslashes consistent, and lower-casing. @@ -59,6 +67,11 @@ namespace VFS::Path bool operator()(std::string_view left, std::string_view right) const { return pathLess(left, right); } }; + inline constexpr auto findSeparatorOrExtensionSeparator(auto begin, auto end) + { + return std::find_if(begin, end, [](char v) { return v == extensionSeparator || v == separator; }); + } + class Normalized; class NormalizedView @@ -153,6 +166,27 @@ namespace VFS::Path operator const std::string&() const { return mValue; } + bool changeExtension(std::string_view extension) + { + if (findSeparatorOrExtensionSeparator(extension.begin(), extension.end()) != extension.end()) + throw std::invalid_argument("Invalid extension: " + std::string(extension)); + const auto it = findSeparatorOrExtensionSeparator(mValue.rbegin(), mValue.rend()); + if (it == mValue.rend() || *it == separator) + return false; + const std::string::difference_type pos = mValue.rend() - it; + mValue.replace(pos, mValue.size(), extension); + normalizeFilenameInPlace(mValue.begin() + pos, mValue.end()); + return true; + } + + Normalized& operator/=(NormalizedView value) + { + mValue.reserve(mValue.size() + value.value().size() + 1); + mValue += separator; + mValue += value.value(); + return *this; + } + friend bool operator==(const Normalized& lhs, const Normalized& rhs) = default; friend bool operator==(const Normalized& lhs, const auto& rhs) { return lhs.mValue == rhs; } @@ -207,6 +241,13 @@ namespace VFS::Path : mValue(value.view()) { } + + inline Normalized operator/(NormalizedView lhs, NormalizedView rhs) + { + Normalized result(lhs); + result /= rhs; + return result; + } } #endif From ec1c6ee1715be1aceb8498d2f7cc881b0472e19d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 24 Feb 2024 14:03:24 +0100 Subject: [PATCH 151/451] Use ESM::decompose to handle ENAMstruct --- apps/openmw_test_suite/esm3/testsaveload.cpp | 29 ++++++++++++++++++++ components/esm3/aipackage.cpp | 12 +++----- components/esm3/effectlist.cpp | 13 +++++++-- components/esm3/effectlist.hpp | 4 --- components/esm3/esmreader.hpp | 11 ++++++++ components/esm3/loadcrea.cpp | 3 +- components/esm3/loadnpc.cpp | 3 +- 7 files changed, 56 insertions(+), 19 deletions(-) diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index eda1fa963e..8c7896b08a 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -525,6 +526,34 @@ namespace ESM EXPECT_EQ(result.mServices, record.mServices); } + TEST_P(Esm3SaveLoadRecordTest, enamShouldNotChange) + { + EffectList record; + record.mList.emplace_back(ENAMstruct{ + .mEffectID = 1, + .mSkill = 2, + .mAttribute = 3, + .mRange = 4, + .mArea = 5, + .mDuration = 6, + .mMagnMin = 7, + .mMagnMax = 8, + }); + + EffectList result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mList.size(), record.mList.size()); + EXPECT_EQ(result.mList[0].mEffectID, record.mList[0].mEffectID); + EXPECT_EQ(result.mList[0].mSkill, record.mList[0].mSkill); + EXPECT_EQ(result.mList[0].mAttribute, record.mList[0].mAttribute); + EXPECT_EQ(result.mList[0].mRange, record.mList[0].mRange); + EXPECT_EQ(result.mList[0].mArea, record.mList[0].mArea); + EXPECT_EQ(result.mList[0].mDuration, record.mList[0].mDuration); + EXPECT_EQ(result.mList[0].mMagnMin, record.mList[0].mMagnMin); + EXPECT_EQ(result.mList[0].mMagnMax, record.mList[0].mMagnMax); + } + INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats())); } } diff --git a/components/esm3/aipackage.cpp b/components/esm3/aipackage.cpp index 2cadb9fb22..33b8a0bca2 100644 --- a/components/esm3/aipackage.cpp +++ b/components/esm3/aipackage.cpp @@ -54,29 +54,25 @@ namespace ESM else if (esm.retSubName() == AI_Wander) { pack.mType = AI_Wander; - esm.getSubHeader(); - esm.getComposite(pack.mWander); + esm.getSubComposite(pack.mWander); mList.push_back(pack); } else if (esm.retSubName() == AI_Travel) { pack.mType = AI_Travel; - esm.getSubHeader(); - esm.getComposite(pack.mTravel); + esm.getSubComposite(pack.mTravel); mList.push_back(pack); } else if (esm.retSubName() == AI_Escort || esm.retSubName() == AI_Follow) { pack.mType = (esm.retSubName() == AI_Escort) ? AI_Escort : AI_Follow; - esm.getSubHeader(); - esm.getComposite(pack.mTarget); + esm.getSubComposite(pack.mTarget); mList.push_back(pack); } else if (esm.retSubName() == AI_Activate) { pack.mType = AI_Activate; - esm.getSubHeader(); - esm.getComposite(pack.mActivate); + esm.getSubComposite(pack.mActivate); mList.push_back(pack); } } diff --git a/components/esm3/effectlist.cpp b/components/esm3/effectlist.cpp index 701552b312..4f21f47fa2 100644 --- a/components/esm3/effectlist.cpp +++ b/components/esm3/effectlist.cpp @@ -3,8 +3,15 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mEffectID, v.mSkill, v.mAttribute, v.mRange, v.mArea, v.mDuration, v.mMagnMin, v.mMagnMax); + } void EffectList::load(ESMReader& esm) { @@ -18,15 +25,15 @@ namespace ESM void EffectList::add(ESMReader& esm) { ENAMstruct s; - esm.getHT(s.mEffectID, s.mSkill, s.mAttribute, s.mRange, s.mArea, s.mDuration, s.mMagnMin, s.mMagnMax); + esm.getSubComposite(s); mList.push_back(s); } void EffectList::save(ESMWriter& esm) const { - for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) + for (const ENAMstruct& enam : mList) { - esm.writeHNT("ENAM", *it, 24); + esm.writeNamedComposite("ENAM", enam); } } diff --git a/components/esm3/effectlist.hpp b/components/esm3/effectlist.hpp index 8f2cb959d6..de13797496 100644 --- a/components/esm3/effectlist.hpp +++ b/components/esm3/effectlist.hpp @@ -9,9 +9,6 @@ namespace ESM class ESMReader; class ESMWriter; -#pragma pack(push) -#pragma pack(1) - /** Defines a spell effect. Shared between SPEL (Spells), ALCH (Potions) and ENCH (Item enchantments) records */ @@ -28,7 +25,6 @@ namespace ESM int32_t mRange; // 0 - self, 1 - touch, 2 - target (RangeType enum) int32_t mArea, mDuration, mMagnMin, mMagnMax; }; -#pragma pack(pop) /// EffectList, ENAM subrecord struct EffectList diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 276adf749c..ca9f191a5d 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -189,6 +189,17 @@ namespace ESM decompose(value, [&](auto&... args) { (getT(args), ...); }); } + void getSubComposite(auto& value) + { + decompose(value, [&](auto&... args) { + constexpr size_t size = (0 + ... + sizeof(decltype(args))); + getSubHeader(); + if (mCtx.leftSub != size) + reportSubSizeMismatch(size, mCtx.leftSub); + (getT(args), ...); + }); + } + template >> void skipHT() { diff --git a/components/esm3/loadcrea.cpp b/components/esm3/loadcrea.cpp index 1db79e8e76..5a0d8048bc 100644 --- a/components/esm3/loadcrea.cpp +++ b/components/esm3/loadcrea.cpp @@ -69,8 +69,7 @@ namespace ESM mSpells.add(esm); break; case fourCC("AIDT"): - esm.getSubHeader(); - esm.getComposite(mAiData); + esm.getSubComposite(mAiData); break; case fourCC("DODT"): case fourCC("DNAM"): diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 92b16638c2..58a8bfa55e 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -102,8 +102,7 @@ namespace ESM mInventory.add(esm); break; case fourCC("AIDT"): - esm.getSubHeader(); - esm.getComposite(mAiData); + esm.getSubComposite(mAiData); break; case fourCC("DODT"): case fourCC("DNAM"): From 7d7e8939abc16301fba58c8975faae72e02b0936 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 24 Feb 2024 16:55:58 +0100 Subject: [PATCH 152/451] Use ESM::decompose to handle WPDTstruct --- apps/openmw/mwclass/creature.cpp | 6 +-- apps/openmw/mwclass/npc.cpp | 6 +-- apps/openmw/mwmechanics/combat.cpp | 18 ++++---- apps/openmw_test_suite/esm3/testsaveload.cpp | 48 ++++++++++++++++++++ components/esm3/esmreader.hpp | 8 +--- components/esm3/loadweap.cpp | 22 ++++++--- components/esm3/loadweap.hpp | 6 +-- 7 files changed, 82 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 2de58c6127..b6c607b415 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -316,11 +316,11 @@ namespace MWClass { const unsigned char* attack = nullptr; if (type == ESM::Weapon::AT_Chop) - attack = weapon.get()->mBase->mData.mChop; + attack = weapon.get()->mBase->mData.mChop.data(); else if (type == ESM::Weapon::AT_Slash) - attack = weapon.get()->mBase->mData.mSlash; + attack = weapon.get()->mBase->mData.mSlash.data(); else if (type == ESM::Weapon::AT_Thrust) - attack = weapon.get()->mBase->mData.mThrust; + attack = weapon.get()->mBase->mData.mThrust.data(); if (attack) { damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b7540ebe04..98384254d3 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -635,11 +635,11 @@ namespace MWClass { const unsigned char* attack = nullptr; if (type == ESM::Weapon::AT_Chop) - attack = weapon.get()->mBase->mData.mChop; + attack = weapon.get()->mBase->mData.mChop.data(); else if (type == ESM::Weapon::AT_Slash) - attack = weapon.get()->mBase->mData.mSlash; + attack = weapon.get()->mBase->mData.mSlash.data(); else if (type == ESM::Weapon::AT_Thrust) - attack = weapon.get()->mBase->mData.mThrust; + attack = weapon.get()->mBase->mData.mThrust.data(); if (attack) { damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 831c3ff7ab..b9852e1b41 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -246,14 +246,16 @@ namespace MWMechanics return; } - const unsigned char* attack = weapon.get()->mBase->mData.mChop; - damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); // Bow/crossbow damage - - // Arrow/bolt damage - // NB in case of thrown weapons, we are applying the damage twice since projectile == weapon - attack = projectile.get()->mBase->mData.mChop; - damage += attack[0] + ((attack[1] - attack[0]) * attackStrength); - + { + const auto& attack = weapon.get()->mBase->mData.mChop; + damage = attack[0] + ((attack[1] - attack[0]) * attackStrength); // Bow/crossbow damage + } + { + // Arrow/bolt damage + // NB in case of thrown weapons, we are applying the damage twice since projectile == weapon + const auto& attack = projectile.get()->mBase->mData.mChop; + damage += attack[0] + ((attack[1] - attack[0]) * attackStrength); + } adjustWeaponDamage(damage, weapon, attacker); } diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index 8c7896b08a..6d5fdf1c14 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -554,6 +555,53 @@ namespace ESM EXPECT_EQ(result.mList[0].mMagnMax, record.mList[0].mMagnMax); } + TEST_P(Esm3SaveLoadRecordTest, weaponShouldNotChange) + { + Weapon record = { + .mData = { + .mWeight = 0, + .mValue = 1, + .mType = 2, + .mHealth = 3, + .mSpeed = 4, + .mReach = 5, + .mEnchant = 6, + .mChop = { 7, 8 }, + .mSlash = { 9, 10 }, + .mThrust = { 11, 12 }, + .mFlags = 13, + }, + .mRecordFlags = 0, + .mId = generateRandomRefId(32), + .mEnchant = generateRandomRefId(32), + .mScript = generateRandomRefId(32), + .mName = generateRandomString(32), + .mModel = generateRandomString(32), + .mIcon = generateRandomString(32), + }; + + Weapon result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mData.mWeight, record.mData.mWeight); + EXPECT_EQ(result.mData.mValue, record.mData.mValue); + EXPECT_EQ(result.mData.mType, record.mData.mType); + EXPECT_EQ(result.mData.mHealth, record.mData.mHealth); + EXPECT_EQ(result.mData.mSpeed, record.mData.mSpeed); + EXPECT_EQ(result.mData.mReach, record.mData.mReach); + EXPECT_EQ(result.mData.mEnchant, record.mData.mEnchant); + EXPECT_EQ(result.mData.mChop, record.mData.mChop); + EXPECT_EQ(result.mData.mSlash, record.mData.mSlash); + EXPECT_EQ(result.mData.mThrust, record.mData.mThrust); + EXPECT_EQ(result.mData.mFlags, record.mData.mFlags); + EXPECT_EQ(result.mId, record.mId); + EXPECT_EQ(result.mEnchant, record.mEnchant); + EXPECT_EQ(result.mScript, record.mScript); + EXPECT_EQ(result.mName, record.mName); + EXPECT_EQ(result.mModel, record.mModel); + EXPECT_EQ(result.mIcon, record.mIcon); + } + INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats())); } } diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index ca9f191a5d..4af2264828 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -191,13 +191,7 @@ namespace ESM void getSubComposite(auto& value) { - decompose(value, [&](auto&... args) { - constexpr size_t size = (0 + ... + sizeof(decltype(args))); - getSubHeader(); - if (mCtx.leftSub != size) - reportSubSizeMismatch(size, mCtx.leftSub); - (getT(args), ...); - }); + decompose(value, [&](auto&... args) { getHT(args...); }); } template >> diff --git a/components/esm3/loadweap.cpp b/components/esm3/loadweap.cpp index 31c03b00fe..f06abf4e7c 100644 --- a/components/esm3/loadweap.cpp +++ b/components/esm3/loadweap.cpp @@ -1,11 +1,20 @@ #include "loadweap.hpp" -#include "components/esm/defs.hpp" +#include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mType, v.mHealth, v.mSpeed, v.mReach, v.mEnchant, v.mChop, v.mSlash, v.mThrust, + v.mFlags); + } + void Weapon::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -29,8 +38,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("WPDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mType, mData.mHealth, mData.mSpeed, mData.mReach, - mData.mEnchant, mData.mChop, mData.mSlash, mData.mThrust, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -68,7 +76,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("WPDT", mData, 32); + esm.writeNamedComposite("WPDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); esm.writeHNOCRefId("ENAM", mEnchant); @@ -84,9 +92,9 @@ namespace ESM mData.mSpeed = 0; mData.mReach = 0; mData.mEnchant = 0; - mData.mChop[0] = mData.mChop[1] = 0; - mData.mSlash[0] = mData.mSlash[1] = 0; - mData.mThrust[0] = mData.mThrust[1] = 0; + mData.mChop.fill(0); + mData.mSlash.fill(0); + mData.mThrust.fill(0); mData.mFlags = 0; mName.clear(); diff --git a/components/esm3/loadweap.hpp b/components/esm3/loadweap.hpp index ba1599b1df..8323176a64 100644 --- a/components/esm3/loadweap.hpp +++ b/components/esm3/loadweap.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_ESM_WEAP_H #define OPENMW_ESM_WEAP_H +#include #include #include "components/esm/refid.hpp" @@ -59,8 +60,6 @@ namespace ESM Silver = 0x02 }; -#pragma pack(push) -#pragma pack(1) struct WPDTstruct { float mWeight; @@ -69,10 +68,9 @@ namespace ESM uint16_t mHealth; float mSpeed, mReach; uint16_t mEnchant; // Enchantment points. The real value is mEnchant/10.f - unsigned char mChop[2], mSlash[2], mThrust[2]; // Min and max + std::array mChop, mSlash, mThrust; // Min and max int32_t mFlags; }; // 32 bytes -#pragma pack(pop) WPDTstruct mData; From a761e417f17b9cb98e1c4d879390dafa1926cabc Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 24 Feb 2024 16:59:11 +0000 Subject: [PATCH 153/451] Accept that it's too much work to defer light manager creation in the CS and instead use something akin to the old approach --- components/sceneutil/glextensions.cpp | 5 +++++ components/sceneutil/glextensions.hpp | 1 + components/sceneutil/lightmanager.cpp | 6 +++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/components/sceneutil/glextensions.cpp b/components/sceneutil/glextensions.cpp index eb55e17b1c..3a14dab8ed 100644 --- a/components/sceneutil/glextensions.cpp +++ b/components/sceneutil/glextensions.cpp @@ -33,6 +33,11 @@ namespace SceneUtil GLExtensionsObserver GLExtensionsObserver::sInstance{}; } + bool glExtensionsReady() + { + return !sGLExtensions.empty(); + } + osg::GLExtensions& getGLExtensions() { if (sGLExtensions.empty()) diff --git a/components/sceneutil/glextensions.hpp b/components/sceneutil/glextensions.hpp index 17a4eb8488..05e7f715c1 100644 --- a/components/sceneutil/glextensions.hpp +++ b/components/sceneutil/glextensions.hpp @@ -6,6 +6,7 @@ namespace SceneUtil { + bool glExtensionsReady(); osg::GLExtensions& getGLExtensions(); class GetGLExtensionsOperation : public osg::GraphicsOperation diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 48efb7fda9..c76f0b6b5c 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -825,9 +825,9 @@ namespace SceneUtil , mPointLightFadeEnd(0.f) , mPointLightFadeStart(0.f) { - osg::GLExtensions& exts = SceneUtil::getGLExtensions(); - bool supportsUBO = exts.isUniformBufferObjectSupported; - bool supportsGPU4 = exts.isGpuShader4Supported; + osg::GLExtensions* exts = SceneUtil::glExtensionsReady() ? &SceneUtil::getGLExtensions() : nullptr; + bool supportsUBO = exts && exts->isUniformBufferObjectSupported; + bool supportsGPU4 = exts && exts->isGpuShader4Supported; mSupported[static_cast(LightingMethod::FFP)] = true; mSupported[static_cast(LightingMethod::PerObjectUniform)] = true; From 65aa222efa3d4a4cf49c8cdfb42e119d89b5220b Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 24 Feb 2024 19:51:08 +0300 Subject: [PATCH 154/451] Move full help text after everything else (#7623) --- CHANGELOG.md | 1 + apps/openmw/mwclass/activator.cpp | 6 ++---- apps/openmw/mwclass/apparatus.cpp | 4 ++-- apps/openmw/mwclass/armor.cpp | 4 ++-- apps/openmw/mwclass/book.cpp | 4 ++-- apps/openmw/mwclass/clothing.cpp | 4 ++-- apps/openmw/mwclass/container.cpp | 6 +++--- apps/openmw/mwclass/creature.cpp | 4 +--- apps/openmw/mwclass/door.cpp | 4 ++-- apps/openmw/mwclass/ingredient.cpp | 4 ++-- apps/openmw/mwclass/light.cpp | 4 ++-- apps/openmw/mwclass/lockpick.cpp | 4 ++-- apps/openmw/mwclass/misc.cpp | 4 ++-- apps/openmw/mwclass/npc.cpp | 2 +- apps/openmw/mwclass/potion.cpp | 4 ++-- apps/openmw/mwclass/probe.cpp | 4 ++-- apps/openmw/mwclass/repair.cpp | 4 ++-- apps/openmw/mwclass/weapon.cpp | 4 ++-- apps/openmw/mwgui/tooltips.cpp | 23 ++++++++++++++++++++++- apps/openmw/mwgui/tooltips.hpp | 1 + 20 files changed, 57 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea44cab4b1..69d0f6c3c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,6 +115,7 @@ Bug #7611: Beast races' idle animations slide after turning or jumping in place Bug #7617: The death prompt asks the player if they wanted to load the character's last created save Bug #7619: Long map notes may get cut off + Bug #7623: Incorrect placement of the script info in the engraved ring of healing tooltip Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing Bug #7633: Groundcover should ignore non-geometry Drawables diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 678c4e054b..e0ee315bc1 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -104,13 +104,11 @@ namespace MWClass std::string_view name = getName(ptr); info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)) + MWGui::ToolTips::getCountString(count); - std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 1bf6f9c845..bf5e4dc2f1 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -102,8 +102,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index ffa5b36a4d..3853f53fc4 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -257,8 +257,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.enchant = ref->mBase->mEnchant; diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 2c375547d0..95453e7a58 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -121,8 +121,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.enchant = ref->mBase->mEnchant; diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 17519405de..cdf51ef663 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -164,8 +164,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.enchant = ref->mBase->mEnchant; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index e2023ef8c3..75b8543b0a 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -265,10 +265,10 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); if (ptr.getCellRef().getRefId() == "stolen_goods") - text += "\nYou can not use evidence chests"; + info.extra += "\nYou cannot use evidence chests"; } info.text = std::move(text); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 2de58c6127..0024755f91 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -591,10 +591,8 @@ namespace MWClass std::string_view name = getName(ptr); info.caption = MyGUI::TextIterator::toTagsString(MyGUI::UString(name)); - std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); - info.text = std::move(text); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); return info; } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 015c454915..7509fbf71f 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -290,8 +290,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 9af9a5703b..e18d6ad5f3 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -117,8 +117,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index dc37b8d154..06b1901864 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -173,8 +173,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 42b5634b64..dc3b73da63 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -118,8 +118,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 0470a89a16..dcd91c51af 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -163,8 +163,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b7540ebe04..43962f44b5 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1118,7 +1118,7 @@ namespace MWClass } if (fullHelp) - info.text = MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra = MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); return info; } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index e5da876d06..42c122cb48 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -114,8 +114,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 4f5e7be5cb..beab45945c 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -117,8 +117,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 3000ea4087..279352263e 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -119,8 +119,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index b5a3415717..5f1f7f2772 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -239,8 +239,8 @@ namespace MWClass if (MWBase::Environment::get().getWindowManager()->getFullHelp()) { - text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); - text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); + info.extra += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); + info.extra += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } info.text = std::move(text); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 0a0343831d..938d4176f2 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -410,10 +410,13 @@ namespace MWGui const std::string& image = info.icon; int imageSize = (!image.empty()) ? info.imageSize : 0; std::string text = info.text; + std::string extra = info.extra; // remove the first newline (easier this way) - if (text.size() > 0 && text[0] == '\n') + if (!text.empty() && text[0] == '\n') text.erase(0, 1); + if (!extra.empty() && extra[0] == '\n') + extra.erase(0, 1); const ESM::Enchantment* enchant = nullptr; const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); @@ -572,6 +575,24 @@ namespace MWGui } } + if (!extra.empty()) + { + Gui::EditBox* extraWidget = mDynamicToolTipBox->createWidget("SandText", + MyGUI::IntCoord(padding.left, totalSize.height + 12, 300 - padding.left, 300 - totalSize.height), + MyGUI::Align::Stretch, "ToolTipExtraText"); + + extraWidget->setEditStatic(true); + extraWidget->setEditMultiLine(true); + extraWidget->setEditWordWrap(info.wordWrap); + extraWidget->setCaptionWithReplacing(extra); + extraWidget->setTextAlign(MyGUI::Align::HCenter | MyGUI::Align::Top); + extraWidget->setNeedKeyFocus(false); + + MyGUI::IntSize extraTextSize = extraWidget->getTextSize(); + totalSize.height += extraTextSize.height + 4; + totalSize.width = std::max(totalSize.width, extraTextSize.width); + } + captionWidget->setCoord((totalSize.width - captionSize.width) / 2 + imageSize, (captionHeight - captionSize.height) / 2, captionSize.width - imageSize, captionSize.height); diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index 69f6856840..132698475f 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -29,6 +29,7 @@ namespace MWGui std::string caption; std::string text; + std::string extra; std::string icon; int imageSize; From fe78e5739155a785d32a51b0d5e783f9760f3466 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 25 Feb 2024 15:17:17 +0300 Subject: [PATCH 155/451] Russian localization updates Localize new player controls lines Fix some other minor inconsistencies --- files/data/l10n/OMWCamera/ru.yaml | 2 +- files/data/l10n/OMWControls/ru.yaml | 73 +++++++++++++++++++++++++++-- files/data/l10n/OMWEngine/ru.yaml | 8 ++-- 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/files/data/l10n/OMWCamera/ru.yaml b/files/data/l10n/OMWCamera/ru.yaml index 2b41ef0ee7..49821157cc 100644 --- a/files/data/l10n/OMWCamera/ru.yaml +++ b/files/data/l10n/OMWCamera/ru.yaml @@ -1,5 +1,5 @@ Camera: "Камера OpenMW" -settingsPageDescription: "Настройки камеры для OpenMW" +settingsPageDescription: "Настройки камеры OpenMW." thirdPersonSettings: "Режим от третьего лица" diff --git a/files/data/l10n/OMWControls/ru.yaml b/files/data/l10n/OMWControls/ru.yaml index 0ce3609e16..e293b7f44d 100644 --- a/files/data/l10n/OMWControls/ru.yaml +++ b/files/data/l10n/OMWControls/ru.yaml @@ -1,5 +1,5 @@ ControlsPage: "Управление OpenMW" -ControlsPageDescription: "Дополнительные настройки, связанные с управлением игроком" +ControlsPageDescription: "Дополнительные настройки, связанные с управлением игроком." MovementSettings: "Движение" @@ -14,5 +14,72 @@ toggleSneakDescription: | чтобы красться, её достаточно нажать единожды для переключения положения, а не зажимать. Игрокам, которые много времени крадутся, может быть проще управлять персонажем, когда опция включена. -# smoothControllerMovement -# smoothControllerMovementDescription +smoothControllerMovement: "Плавное движение на геймпаде" +smoothControllerMovementDescription: | + Эта настройка сглаживает движение при использовании стика. Это делает переход от ходьбы к бегу не таким резким. + +TogglePOV_name: "Изменить вид" +TogglePOV_description: "Переключиться между видами от первого и третьего лица. При зажатии будет включен режим предпросмотра." + +Zoom3rdPerson_name: "Приблизить/отдалить камеру" +Zoom3rdPerson_description: "Приблизить или отдалить от персонажа камеру в виде от третьего лица." + +MoveForward_name: "Двигаться вперед" +MoveForward_description: "Одновременное зажатие кнопки движения назад отменит движение." + +MoveBackward_name: "Двигаться назад" +MoveBackward_description: "Одновременное зажатие кнопки движения вперед отменит движение." + +MoveLeft_name: "Двигаться влево" +MoveLeft_description: "Одновременное зажатие кнопки движения вправо отменит движение." + +MoveRight_name: "Двигаться вправо" +MoveRight_description: "Одновременное зажатие кнопки движения влево отменит движение." + +Use_name: "Использовать" +Use_description: "Совершить атаку оружием или использовать заклинание в зависимости от текущей стойки." + +Run_name: "Бежать" +Run_description: "Инвертировать состояние бега/ходьбы при зажатии -- состояние по умолчанию зависит от настройки постоянного бега." + +AlwaysRun_name: "Постоянный бег" +AlwaysRun_description: "Переключить настройку постоянного бега." + +Jump_name: "Прыгать" +Jump_description: "Прыгнуть, если вы находитесь на земле." + +AutoMove_name: "Автоматический бег" +AutoMove_description: "Переключить непрерывное движение вперед." + +Sneak_name: "Красться" +Sneak_description: "Красться при зажатии, если настройка переключения движения крадучись выключена." + +ToggleSneak_name: "Переключить движение крадучись" +ToggleSneak_description: "Переключить движение крадучись, если соответствующая настройка включена." + +ToggleWeapon_name: "Приготовить оружие" +ToggleWeapon_description: "Принять стойку оружия или покинуть её." + +ToggleSpell_name: "Приготовить магию" +ToggleSpell_description: "Принять стойку магии или покинуть её." + +Inventory_name: "Инвентарь" +Inventory_description: "Открыть инвентарь." + +Journal_name: "Дневник" +Journal_description: "Открыть дневник." + +QuickKeysMenu_name: "Меню быстрых клавиш" +QuickKeysMenu_description: "Открыть меню быстрых клавиш." + +SmoothMoveForward_name: "Плавно двигаться вперед" +SmoothMoveForward_description: "Движение вперед с плавными переходами от ходьбы к бегу." + +SmoothMoveBackward_name: "Плавно двигаться назад" +SmoothMoveBackward_description: "Движение назад с плавными переходами от ходьбы к бегу." + +SmoothMoveLeft_name: "Плавно двигаться влево" +SmoothMoveLeft_description: "Движение влево с плавными переходами от ходьбы к бегу." + +SkmoothMoveRight_name: "Плавно двигаться вправо" +SkmoothMoveRight_description: "Движение вправо с плавными переходами от ходьбы к бегу." diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index a9f396f73c..07fc376675 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -135,6 +135,7 @@ RainRippleDetail: "Капли дождя на воде" RainRippleDetailDense: "Плотные" RainRippleDetailSimple: "Упрощенные" RainRippleDetailSparse: "Редкие" +RebindAction: "Нажмите клавишу, которую нужно назначить на это действие." ReflectionShaderDetail: "Детализация отражений" ReflectionShaderDetailActors: "Персонажи" ReflectionShaderDetailGroundcover: "Трава" @@ -143,7 +144,6 @@ ReflectionShaderDetailSky: "Небо" ReflectionShaderDetailTerrain: "Ландшафт" ReflectionShaderDetailWorld: "Мир" Refraction: "Рефракция" -RebindAction: "Нажмите клавишу, которую нужно назначить на это действие." ResetControls: "Сбросить" Screenshot: "Снимок экрана" Scripts: "Скрипты" @@ -154,17 +154,17 @@ SensitivityHigh: "Высокая" SensitivityLow: "Низкая" SettingsWindow: "Настройки" Subtitles: "Субтитры" -TestingExteriorCells: "Проверка наружних ячеек" +TestingExteriorCells: "Проверка наружных ячеек" TestingInteriorCells: "Проверка ячеек-помещений" TextureFiltering: "Фильтрация текстур" TextureFilteringBilinear: "Билинейная" TextureFilteringDisabled: "Отключена" TextureFilteringOther: "Другая" TextureFilteringTrilinear: "Трилинейная" -TransparencyFull: "Прозрачное" -TransparencyNone: "Непрозрачное" ToggleHUD: "Переключить HUD" TogglePostProcessorHUD: "Меню настроек постобработки" +TransparencyFull: "Прозрачное" +TransparencyNone: "Непрозрачное" ViewDistance: "Дальность обзора" Video: "Видео" VSync: "Вертикальная синхронизация" From 25b6230a546b3b9d4fb205967d1ac902ea3deec2 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 25 Feb 2024 16:13:45 +0300 Subject: [PATCH 156/451] Fix Smooth Move Right action localization --- files/data/l10n/OMWControls/en.yaml | 4 ++-- files/data/l10n/OMWControls/ru.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/files/data/l10n/OMWControls/en.yaml b/files/data/l10n/OMWControls/en.yaml index c034eb8683..d4df56436b 100644 --- a/files/data/l10n/OMWControls/en.yaml +++ b/files/data/l10n/OMWControls/en.yaml @@ -80,5 +80,5 @@ SmoothMoveBackward_description: "Backward movement adjusted for smooth Walk-Run SmoothMoveLeft_name: "Smooth Move Left" SmoothMoveLeft_description: "Left movement adjusted for smooth Walk-Run transitions." -SkmoothMoveRight_name: "Smooth Move Right" -SkmoothMoveRight_description: "Right movement adjusted for smooth Walk-Run transitions." +SmoothMoveRight_name: "Smooth Move Right" +SmoothMoveRight_description: "Right movement adjusted for smooth Walk-Run transitions." diff --git a/files/data/l10n/OMWControls/ru.yaml b/files/data/l10n/OMWControls/ru.yaml index e293b7f44d..4c7f6d379e 100644 --- a/files/data/l10n/OMWControls/ru.yaml +++ b/files/data/l10n/OMWControls/ru.yaml @@ -81,5 +81,5 @@ SmoothMoveBackward_description: "Движение назад с плавными SmoothMoveLeft_name: "Плавно двигаться влево" SmoothMoveLeft_description: "Движение влево с плавными переходами от ходьбы к бегу." -SkmoothMoveRight_name: "Плавно двигаться вправо" -SkmoothMoveRight_description: "Движение вправо с плавными переходами от ходьбы к бегу." +SmoothMoveRight_name: "Плавно двигаться вправо" +SmoothMoveRight_description: "Движение вправо с плавными переходами от ходьбы к бегу." From 059191c84069e8edca3b9ac1d48d032b0969508a Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Sun, 25 Feb 2024 07:30:23 -0600 Subject: [PATCH 157/451] Also apply hasWaterHeightSub for INTV --- components/esm3/loadcell.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 473c4c7d72..0c37e64f1e 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -127,6 +127,7 @@ namespace ESM case fourCC("INTV"): int32_t waterl; esm.getHT(waterl); + mHasWaterHeightSub = true; mWater = static_cast(waterl); break; case fourCC("WHGT"): From ba78729f35097be942b17f4d1965a1fbe034bf39 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 25 Feb 2024 16:42:18 +0300 Subject: [PATCH 158/451] Make Run action ru description more faithful to en description --- files/data/l10n/OMWControls/ru.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/l10n/OMWControls/ru.yaml b/files/data/l10n/OMWControls/ru.yaml index 4c7f6d379e..4675321969 100644 --- a/files/data/l10n/OMWControls/ru.yaml +++ b/files/data/l10n/OMWControls/ru.yaml @@ -40,7 +40,7 @@ Use_name: "Использовать" Use_description: "Совершить атаку оружием или использовать заклинание в зависимости от текущей стойки." Run_name: "Бежать" -Run_description: "Инвертировать состояние бега/ходьбы при зажатии -- состояние по умолчанию зависит от настройки постоянного бега." +Run_description: "Бежать или идти при зажатии (в зависимости от значения настройки постоянного бега)." AlwaysRun_name: "Постоянный бег" AlwaysRun_description: "Переключить настройку постоянного бега." From 357bf3db61ca4dcb44569b00560bc956579b9aab Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 25 Feb 2024 14:01:20 +0000 Subject: [PATCH 159/451] Load all config files --- apps/launcher/maindialog.cpp | 10 ++-------- components/files/qtconfigpath.hpp | 10 ++++++++++ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 5d558ef38f..f9d07d54a5 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -349,17 +349,11 @@ bool Launcher::MainDialog::setupGameSettings() if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readUserFile)) return false; - // Now the rest - priority: user > local > global - if (auto result = loadFile(Files::getLocalConfigPathQString(mCfgMgr), &Config::GameSettings::readFile, true)) + for (const auto& path : Files::getActiveConfigPathsQString(mCfgMgr)) { - // Load global if local wasn't found - if (!*result && !loadFile(Files::getGlobalConfigPathQString(mCfgMgr), &Config::GameSettings::readFile, true)) + if (!loadFile(path, &Config::GameSettings::readFile)) return false; } - else - return false; - if (!loadFile(Files::getUserConfigPathQString(mCfgMgr), &Config::GameSettings::readFile)) - return false; return true; } diff --git a/components/files/qtconfigpath.hpp b/components/files/qtconfigpath.hpp index e6d7b4202c..16e0499cd5 100644 --- a/components/files/qtconfigpath.hpp +++ b/components/files/qtconfigpath.hpp @@ -22,6 +22,16 @@ namespace Files { return Files::pathToQString(cfgMgr.getGlobalPath() / openmwCfgFile); } + + inline QStringList getActiveConfigPathsQString(const Files::ConfigurationManager& cfgMgr) + { + const auto& activePaths = cfgMgr.getActiveConfigPaths(); + QStringList result; + result.reserve(static_cast(activePaths.size())); + for (const auto& path : activePaths) + result.append(Files::pathToQString(path / openmwCfgFile)); + return result; + } } #endif // OPENMW_COMPONENTS_FILES_QTCONFIGPATH_H From 6b07718871a616ec8d73ce882e3c6075571badfd Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 25 Feb 2024 01:01:55 +0100 Subject: [PATCH 160/451] Add morrowind test for moving object into container --- scripts/data/morrowind_tests/test.lua | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/scripts/data/morrowind_tests/test.lua b/scripts/data/morrowind_tests/test.lua index 8898420b82..3515002f2d 100644 --- a/scripts/data/morrowind_tests/test.lua +++ b/scripts/data/morrowind_tests/test.lua @@ -2,6 +2,7 @@ local testing = require('testing_util') local util = require('openmw.util') local world = require('openmw.world') local core = require('openmw.core') +local types = require('openmw.types') if not core.contentFiles.has('Morrowind.esm') then error('This test requires Morrowind.esm') @@ -18,6 +19,28 @@ local tests = { coroutine.yield() testing.runLocalTest(world.players[1], 'Guard in Imperial Prison Ship should find path (#7241)') end}, + {'Should keep reference to an object moved into container (#7663)', function() + world.players[1]:teleport('ToddTest', util.vector3(2176, 3648, -191), util.transform.rotateZ(math.rad(0))) + coroutine.yield() + local barrel = world.createObject('barrel_01', 1) + local fargothRing = world.createObject('ring_keley', 1) + coroutine.yield() + testing.expectEqual(types.Container.inventory(barrel):find('ring_keley'), nil) + fargothRing:moveInto(types.Container.inventory(barrel)) + coroutine.yield() + testing.expectEqual(fargothRing.recordId, 'ring_keley') + local isFargothRing = function(actual) + if actual == nil then + return 'ring_keley is not found' + end + if actual.id ~= fargothRing.id then + return 'found ring_keley id does not match expected: actual=' .. tostring(actual.id) + .. ', expected=' .. tostring(fargothRing.id) + end + return '' + end + testing.expectThat(types.Container.inventory(barrel):find('ring_keley'), isFargothRing) + end}, } return { From 86a82ae3f11e9d39270988dd89db17e863ceca94 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 25 Feb 2024 13:13:28 +0100 Subject: [PATCH 161/451] Open matching version of documentation for Launcher Help --- CMakeLists.txt | 2 +- components/CMakeLists.txt | 3 ++- components/misc/helpviewer.cpp | 5 ++++- components/version/version.cpp.in | 7 +++++++ components/version/version.hpp | 2 ++ 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76aede04c9..c3c1e47500 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,7 +89,7 @@ set(OPENMW_VERSION_COMMITDATE "") set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") -set(OPENMW_DOC_BASEURL "https://openmw.readthedocs.io/en/stable/") +set(OPENMW_DOC_BASEURL "https://openmw.readthedocs.io/en/") set(GIT_CHECKOUT FALSE) if(EXISTS ${PROJECT_SOURCE_DIR}/.git) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6ab7fc4795..65c431cfd6 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -28,6 +28,7 @@ if (GIT_CHECKOUT) -DOPENMW_LUA_API_REVISION=${OPENMW_LUA_API_REVISION} -DOPENMW_POSTPROCESSING_API_REVISION=${OPENMW_POSTPROCESSING_API_REVISION} -DOPENMW_VERSION=${OPENMW_VERSION} + -DOPENMW_DOC_BASEURL=${OPENMW_DOC_BASEURL} -DMACROSFILE=${CMAKE_SOURCE_DIR}/cmake/OpenMWMacros.cmake "-DCMAKE_CONFIGURATION_TYPES=${CMAKE_CONFIGURATION_TYPES}" -Dgenerator_is_multi_config_var=${multi_config} @@ -596,7 +597,6 @@ endif() if (USE_QT) add_library(components_qt STATIC ${COMPONENT_QT_FILES} ${ESM_UI_HDR}) target_link_libraries(components_qt components Qt::Widgets Qt::Core) - target_compile_definitions(components_qt PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") if (BUILD_LAUNCHER OR BUILD_WIZARD) add_dependencies(components_qt qm-files) @@ -636,6 +636,7 @@ endif() set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE) target_compile_definitions(components PUBLIC BT_USE_DOUBLE_PRECISION) +target_compile_definitions(components PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") if(OSG_STATIC) unset(_osg_plugins_static_files) diff --git a/components/misc/helpviewer.cpp b/components/misc/helpviewer.cpp index 0ff4abb9d3..ebfca9ad14 100644 --- a/components/misc/helpviewer.cpp +++ b/components/misc/helpviewer.cpp @@ -4,9 +4,12 @@ #include #include +#include + void Misc::HelpViewer::openHelp(const char* url) { - QString link{ OPENMW_DOC_BASEURL }; + std::string_view docsUrl = Version::getDocumentationUrl(); + QString link = QString::fromUtf8(docsUrl.data(), docsUrl.size()); link.append(url); QDesktopServices::openUrl(QUrl(link)); } diff --git a/components/version/version.cpp.in b/components/version/version.cpp.in index 312520acbb..12192785b7 100644 --- a/components/version/version.cpp.in +++ b/components/version/version.cpp.in @@ -52,4 +52,11 @@ namespace Version return getVersion() == version && getCommitHash() == commitHash && getTagHash() == tagHash; } + std::string_view getDocumentationUrl() + { + if constexpr (std::string_view("@OPENMW_VERSION_COMMITHASH@") == "@OPENMW_VERSION_TAGHASH@") + return OPENMW_DOC_BASEURL "openmw-@OPENMW_VERSION_MAJOR@.@OPENMW_VERSION_MINOR@.@OPENMW_VERSION_RELEASE@/"; + else + return OPENMW_DOC_BASEURL "latest/"; + } } diff --git a/components/version/version.hpp b/components/version/version.hpp index c05cf8a594..a8a8117dee 100644 --- a/components/version/version.hpp +++ b/components/version/version.hpp @@ -17,6 +17,8 @@ namespace Version std::string getOpenmwVersionDescription(); bool checkResourcesVersion(const std::filesystem::path& resourcePath); + + std::string_view getDocumentationUrl(); } #endif // VERSION_HPP From 42c7fc8e921272d18830054fd217e6188b81a110 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 25 Feb 2024 18:53:19 +0000 Subject: [PATCH 162/451] Update 2 files - /components/CMakeLists.txt - /cmake/GitVersion.cmake --- cmake/GitVersion.cmake | 2 +- components/CMakeLists.txt | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cmake/GitVersion.cmake b/cmake/GitVersion.cmake index a77b0d5b0a..44b57b17d4 100644 --- a/cmake/GitVersion.cmake +++ b/cmake/GitVersion.cmake @@ -29,4 +29,4 @@ endif () include(${MACROSFILE}) configure_resource_file(${VERSION_RESOURCE_FILE_IN} ${OpenMW_BINARY_DIR} ${VERSION_RESOURCE_FILE_RELATIVE}) -configure_file("${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") +configure_file("${VERSION_CPP_FILE_IN}" "${VERSION_CPP_FILE_OUT}") diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 6ab7fc4795..730423d84e 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -12,8 +12,8 @@ set (VERSION_CPP_FILE "components/version/version.cpp") if (GIT_CHECKOUT) get_generator_is_multi_config(multi_config) - add_custom_command ( - OUTPUT "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}" + add_custom_target ( + BYPRODUCTS "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}" DEPENDS "${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in" COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} @@ -21,7 +21,8 @@ if (GIT_CHECKOUT) -DOpenMW_BINARY_DIR=${OpenMW_BINARY_DIR} -DVERSION_RESOURCE_FILE_IN=${VERSION_RESOURCE_FILE_IN} -DVERSION_RESOURCE_FILE_RELATIVE=${VERSION_RESOURCE_FILE_RELATIVE} - -DVERSION_CPP_FILE=${VERSION_CPP_FILE} + -DVERSION_CPP_FILE_IN=${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in + -DVERSION_CPP_FILE_OUT=${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}.out -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} @@ -32,7 +33,9 @@ if (GIT_CHECKOUT) "-DCMAKE_CONFIGURATION_TYPES=${CMAKE_CONFIGURATION_TYPES}" -Dgenerator_is_multi_config_var=${multi_config} -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake - VERBATIM) + COMMAND ${CMAKE_COMMAND} + -E copy_if_different ${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}.out ${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE} + VERBATIM) else (GIT_CHECKOUT) configure_resource_file(${VERSION_RESOURCE_FILE_IN} ${OpenMW_BINARY_DIR} ${VERSION_RESOURCE_FILE_RELATIVE}) configure_file("${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") From 93a84b38ac47d31d6e3131130f3a077e07097cb3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 25 Feb 2024 19:05:38 +0000 Subject: [PATCH 163/451] Give git-version its name back --- components/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 730423d84e..4b3a661253 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -12,7 +12,7 @@ set (VERSION_CPP_FILE "components/version/version.cpp") if (GIT_CHECKOUT) get_generator_is_multi_config(multi_config) - add_custom_target ( + add_custom_target (get-version BYPRODUCTS "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}" DEPENDS "${OpenMW_SOURCE_DIR}/${VERSION_CPP_FILE}.in" COMMAND ${CMAKE_COMMAND} From 02ef7ae3ccd1512e35165f75ae6974f2a053a028 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 25 Feb 2024 22:49:53 +0000 Subject: [PATCH 164/451] Give up rearranging the CS --- components/resource/imagemanager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 124ff9b6ad..09c7048059 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -66,6 +66,8 @@ namespace Resource case (GL_COMPRESSED_RGBA_S3TC_DXT3_EXT): case (GL_COMPRESSED_RGBA_S3TC_DXT5_EXT): { + if (!SceneUtil::glExtensionsReady()) + return true; // hashtag yolo (CS might not have context when loading assets) osg::GLExtensions& exts = SceneUtil::getGLExtensions(); if (!exts.isTextureCompressionS3TCSupported // This one works too. Should it be included in isTextureCompressionS3TCSupported()? Submitted as a From bcd54ab1ff30facaafa9c9ccc6a37c1c223c5048 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 25 Feb 2024 23:01:52 +0000 Subject: [PATCH 165/451] Format osgpluginchecker.cpp.in I formatted the generated file that's part of the VS solution, then diffed it against the input and changed it to match. --- components/misc/osgpluginchecker.cpp.in | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index e58c6c59b2..b570c8f858 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -41,13 +41,13 @@ namespace Misc for (const auto& path : filepath) { #ifdef OSG_USE_UTF8_FILENAME - std::filesystem::path osgPath {stringToU8String(path)}; + std::filesystem::path osgPath{ stringToU8String(path) }; #else - std::filesystem::path osgPath {path}; + std::filesystem::path osgPath{ path }; #endif if (!osgPath.has_filename()) osgPath = osgPath.parent_path(); - + if (osgPath.filename() == pluginDirectoryName) { osgPath = osgPath.parent_path(); @@ -64,14 +64,16 @@ namespace Misc bool haveAllPlugins = true; for (std::string_view plugin : USED_OSG_PLUGIN_FILENAMES) { - if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), [&](std::string_view availablePlugin) { + if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), + [&](std::string_view availablePlugin) { #ifdef OSG_USE_UTF8_FILENAME - std::filesystem::path pluginPath {stringToU8String(availablePlugin)}; + std::filesystem::path pluginPath{ stringToU8String(availablePlugin) }; #else std::filesystem::path pluginPath {availablePlugin}; #endif - return pluginPath.filename() == plugin; - }) == availableOSGPlugins.end()) + return pluginPath.filename() == plugin; + }) + == availableOSGPlugins.end()) { Log(Debug::Error) << "Missing OSG plugin: " << plugin; haveAllPlugins = false; From 98447a16909f9703886bd12b6acad48472e124c8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 26 Feb 2024 12:48:59 +0300 Subject: [PATCH 166/451] Remove legacy ownership documentation --- files/lua_api/openmw/core.lua | 4 ++-- files/lua_api/openmw/self.lua | 9 --------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 330f0e20a0..66fb817362 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -142,9 +142,9 @@ -- @return #string -- @usage if obj.recordId == core.getFormId('Skyrim.esm', 0x4d7da) then ... end -- @usage -- In ESM3 content files (e.g. Morrowind) ids are human-readable strings --- obj.ownerFactionId = 'blades' +-- obj.owner.factionId = 'blades' -- -- In ESM4 (e.g. Skyrim) ids should be constructed using `core.getFormId`: --- obj.ownerFactionId = core.getFormId('Skyrim.esm', 0x72834) +-- obj.owner.factionId = core.getFormId('Skyrim.esm', 0x72834) -- @usage -- local scripts -- local obj = nearby.getObjectByFormId(core.getFormId('Morrowind.esm', 128964)) -- @usage -- global scripts diff --git a/files/lua_api/openmw/self.lua b/files/lua_api/openmw/self.lua index 005017e5c3..6123c36ae6 100644 --- a/files/lua_api/openmw/self.lua +++ b/files/lua_api/openmw/self.lua @@ -22,15 +22,6 @@ -- The object the script is attached to (readonly) -- @field [parent=#self] openmw.core#GameObject object ---- NPC who owns the object or `nil` (mutable). --- @field [parent=#self] #string ownerRecordId - ---- Faction who owns the object or `nil` (mutable). --- @field [parent=#self] #string ownerFactionId - ---- Rank required to be allowed to pick up the object (mutable). --- @field [parent=#self] #number ownerFactionRank - --- -- Movement controls (only for actors) From 626f438dcc4de7a2581f630356dce91bd5717cba Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 27 Feb 2024 01:09:46 +0000 Subject: [PATCH 167/451] Make builtin.omwscripts actually mandatory Previously it was quasi-mandatory - lots of things would add it, e.g. when running openmw through the CS, but it could technically be disabled. Now it's treated like the resources/vfs directory and implicitly added by the engine etc. --- apps/bulletobjecttool/main.cpp | 4 +++- apps/navmeshtool/main.cpp | 4 +++- apps/opencs/model/doc/runner.cpp | 1 - apps/openmw/main.cpp | 3 ++- components/contentselector/model/contentmodel.cpp | 4 ---- files/openmw.cfg | 1 - files/openmw.cfg.local | 1 - 7 files changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index 884c196e53..b27c8135d6 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -146,7 +146,9 @@ namespace dataDirs.insert(dataDirs.begin(), resDir / "vfs"); const Files::Collections fileCollections(dataDirs); const auto& archives = variables["fallback-archive"].as(); - const auto& contentFiles = variables["content"].as(); + StringsVector contentFiles{ "builtin.omwscripts" }; + const auto& configContentFiles = variables["content"].as(); + contentFiles.insert(contentFiles.end(), configContentFiles.begin(), configContentFiles.end()); Fallback::Map::init(variables["fallback"].as().mMap); diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 3ec34114af..94ab7ef082 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -165,7 +165,9 @@ namespace NavMeshTool dataDirs.insert(dataDirs.begin(), resDir / "vfs"); const Files::Collections fileCollections(dataDirs); const auto& archives = variables["fallback-archive"].as(); - const auto& contentFiles = variables["content"].as(); + StringsVector contentFiles{ "builtin.omwscripts" }; + const auto& configContentFiles = variables["content"].as(); + contentFiles.insert(contentFiles.end(), configContentFiles.begin(), configContentFiles.end()); const std::size_t threadsNumber = variables["threads"].as(); if (threadsNumber < 1) diff --git a/apps/opencs/model/doc/runner.cpp b/apps/opencs/model/doc/runner.cpp index 0099cb2f94..d647d6b498 100644 --- a/apps/opencs/model/doc/runner.cpp +++ b/apps/opencs/model/doc/runner.cpp @@ -93,7 +93,6 @@ void CSMDoc::Runner::start(bool delayed) arguments << "--data=\"" + Files::pathToQString(mProjectPath.parent_path()) + "\""; arguments << "--replace=content"; - arguments << "--content=builtin.omwscripts"; for (const auto& mContentFile : mContentFiles) { diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 5bbc0211c1..beaa452f19 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -108,7 +108,8 @@ bool parseOptions(int argc, char** argv, OMW::Engine& engine, Files::Configurati Log(Debug::Error) << "No content file given (esm/esp, nor omwgame/omwaddon). Aborting..."; return false; } - std::set contentDedupe; + engine.addContentFile("builtin.omwscripts"); + std::set contentDedupe{ "builtin.omwscripts" }; for (const auto& contentFile : content) { if (!contentDedupe.insert(contentFile).second) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index d800112712..377edc3b1c 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -434,10 +434,6 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf { QFileInfo info(dir.absoluteFilePath(path2)); - // Enabled by default in system openmw.cfg; shouldn't be shown in content list. - if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0) - continue; - EsmFile* file = const_cast(item(info.fileName())); bool add = file == nullptr; std::unique_ptr newFile; diff --git a/files/openmw.cfg b/files/openmw.cfg index 37ecda3b1d..20e11323cf 100644 --- a/files/openmw.cfg +++ b/files/openmw.cfg @@ -2,7 +2,6 @@ # Modifications should be done on the user openmw.cfg file instead # (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html) -content=builtin.omwscripts data-local="?userdata?data" user-data="?userdata?" config="?userconfig?" diff --git a/files/openmw.cfg.local b/files/openmw.cfg.local index c9949f2447..65f8b31136 100644 --- a/files/openmw.cfg.local +++ b/files/openmw.cfg.local @@ -2,7 +2,6 @@ # Modifications should be done on the user openmw.cfg file instead # (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html) -content=builtin.omwscripts data-local="?userdata?data" user-data="?userdata?" config="?userconfig?" From 90966ecc477bd14661a862e2bd9915f94bbf59dd Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 27 Feb 2024 01:39:49 +0000 Subject: [PATCH 168/451] Handle replace= lines properly in the launcher --- components/config/gamesettings.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index ad7c73d3d9..339acd01fe 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -98,8 +98,32 @@ bool Config::GameSettings::readUserFile(QTextStream& stream, bool ignoreContent) bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap& settings, bool ignoreContent) { QMultiMap cache; + QRegularExpression replaceRe("^\\s*replace\\s*=\\s*(.+)$"); QRegularExpression keyRe("^([^=]+)\\s*=\\s*(.+)$"); + auto initialPos = stream.pos(); + + while (!stream.atEnd()) + { + QString line = stream.readLine(); + + if (line.isEmpty() || line.startsWith("#")) + continue; + + QRegularExpressionMatch match = keyRe.match(line); + if (match.hasMatch()) + { + QString key = match.captured(1).trimmed(); + // Replace composing entries with a replace= line + if (key == QLatin1String("data") || key == QLatin1String("fallback-archive") + || key == QLatin1String("content") || key == QLatin1String("groundcover") + || key == QLatin1String("script-blacklist")) + settings.remove(key); + } + } + + stream.seek(initialPos); + while (!stream.atEnd()) { QString line = stream.readLine(); From dbdecfe94b814a85a16aac0760d7dce6789c29cc Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 27 Feb 2024 01:41:12 +0000 Subject: [PATCH 169/451] Use approved safety comment for path escaping explanation I thought I'd got this one already --- components/config/gamesettings.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 339acd01fe..cf0f7e382d 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -147,8 +147,11 @@ bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap Date: Tue, 27 Feb 2024 02:14:31 +0000 Subject: [PATCH 170/451] data-local is already unquoted when it's read --- components/config/gamesettings.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index cf0f7e382d..53924c4313 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -47,11 +47,6 @@ void Config::GameSettings::validatePaths() // Do the same for data-local QString local = mSettings.value(QString("data-local")); - if (local.length() && local.at(0) == QChar('\"')) - { - local.remove(0, 1); - local.chop(1); - } if (local.isEmpty()) return; From f476301670d208690f660a054a3463d35b80142a Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 27 Feb 2024 14:11:48 +0000 Subject: [PATCH 171/451] There's no such thing as the global data directory That's what resources/vfs is for. --- apps/launcher/datafilespage.cpp | 10 +++++----- components/config/gamesettings.cpp | 10 ++++------ components/config/gamesettings.hpp | 3 ++- components/config/launchersettings.cpp | 6 +++--- components/files/configurationmanager.cpp | 5 ----- components/files/configurationmanager.hpp | 1 - 6 files changed, 14 insertions(+), 21 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 92b86e9cec..a0287afd87 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -279,9 +279,9 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) if (!mDataLocal.isEmpty()) directories.insert(0, mDataLocal); - const auto& globalDataDir = mGameSettings.getGlobalDataDir(); - if (!globalDataDir.empty()) - directories.insert(0, Files::pathToQString(globalDataDir)); + const auto& resourcesVfs = mGameSettings.getResourcesVfs(); + if (!resourcesVfs.isEmpty()) + directories.insert(0, resourcesVfs); std::unordered_set visitedDirectories; for (const QString& currentDir : directories) @@ -315,8 +315,8 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) item->setFont(font); } - // deactivate data-local and global data directory: they are always included - if (currentDir == mDataLocal || Files::pathFromQString(currentDir) == globalDataDir) + // deactivate data-local and resources/vfs: they are always included + if (currentDir == mDataLocal || currentDir == resourcesVfs) { auto flags = item->flags(); item->setFlags(flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled)); diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 53924c4313..f8ff3d362c 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -64,13 +64,11 @@ void Config::GameSettings::validatePaths() } } -std::filesystem::path Config::GameSettings::getGlobalDataDir() const +QString Config::GameSettings::getResourcesVfs() const { - // global data dir may not exists if OpenMW is not installed (ie if run from build directory) - const auto& path = mCfgMgr.getGlobalDataPath(); - if (std::filesystem::exists(path)) - return std::filesystem::canonical(path); - return {}; + QString resources = mSettings.value(QString("resources"), QString("./resources")); + resources += "/vfs"; + return QFileInfo(resources).canonicalFilePath(); } QStringList Config::GameSettings::values(const QString& key, const QStringList& defaultValues) const diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index 34e3dc73ea..bef108e2c7 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -53,7 +53,8 @@ namespace Config } QStringList getDataDirs() const; - std::filesystem::path getGlobalDataDir() const; + + QString getResourcesVfs() const; inline void removeDataDir(const QString& dir) { diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index 9d12535619..2f4decb762 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -233,10 +233,10 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) return; } - // global and local data directories are not part of any profile - const auto globalDataDir = Files::pathToQString(gameSettings.getGlobalDataDir()); + // local data directory and resources/vfs are not part of any profile + const auto resourcesVfs = gameSettings.getResourcesVfs(); const auto dataLocal = gameSettings.getDataLocal(); - dirs.removeAll(globalDataDir); + dirs.removeAll(resourcesVfs); dirs.removeAll(dataLocal); // if any existing profile in launcher matches the content list, make that profile the default diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 210261cdf4..10e10375bb 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -411,11 +411,6 @@ namespace Files return mFixedPath.getLocalPath(); } - const std::filesystem::path& ConfigurationManager::getGlobalDataPath() const - { - return mFixedPath.getGlobalDataPath(); - } - const std::filesystem::path& ConfigurationManager::getCachePath() const { return mFixedPath.getCachePath(); diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 8a5bca0869..aec7799fea 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -45,7 +45,6 @@ namespace Files const std::filesystem::path& getGlobalPath() const; const std::filesystem::path& getLocalPath() const; - const std::filesystem::path& getGlobalDataPath() const; const std::filesystem::path& getUserConfigPath() const; const std::filesystem::path& getUserDataPath() const; const std::filesystem::path& getLocalDataPath() const; From c82c111ee163d9a897b43764302ed29113403f99 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 27 Feb 2024 19:28:51 +0100 Subject: [PATCH 172/451] Use correct index for Athletics_SwimOneSecond --- components/esm3/loadskil.hpp | 2 +- files/data/scripts/omw/skillhandlers.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/esm3/loadskil.hpp b/components/esm3/loadskil.hpp index 9cae87903c..d8e365aca1 100644 --- a/components/esm3/loadskil.hpp +++ b/components/esm3/loadskil.hpp @@ -75,7 +75,7 @@ namespace ESM Speechcraft_Fail = 1, Armorer_Repair = 0, Athletics_RunOneSecond = 0, - Athletics_SwimOneSecond = 0, + Athletics_SwimOneSecond = 1, }; diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index f6a8ec4248..57fc224cee 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -32,7 +32,7 @@ local Skill = core.stats.Skill -- @field #number Speechcraft_Fail 1 -- @field #number Armorer_Repair 0 -- @field #number Athletics_RunOneSecond 0 --- @field #number Athletics_SwimOneSecond 0 +-- @field #number Athletics_SwimOneSecond 1 --- -- Table of valid sources for skill increases @@ -240,7 +240,7 @@ return { Speechcraft_Fail = 1, Armorer_Repair = 0, Athletics_RunOneSecond = 0, - Athletics_SwimOneSecond = 0, + Athletics_SwimOneSecond = 1, }, --- Trigger a skill level up, activating relevant handlers From ddd09456453f31bcefc18688655edd7b8165e00f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 24 Feb 2024 15:57:11 +0400 Subject: [PATCH 173/451] Add a storage mode to drop section on game exit --- CMakeLists.txt | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 9 +++--- components/lua/storage.cpp | 46 ++++++++++++++++++++++------- components/lua/storage.hpp | 22 ++++++++++---- files/lua_api/openmw/storage.lua | 29 ++++++++++++++++-- 5 files changed, 83 insertions(+), 25 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76aede04c9..ca25fd05ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,7 +80,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 55) +set(OPENMW_LUA_API_REVISION 56) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 4e16b396cd..256a11a0b6 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -112,13 +112,12 @@ namespace MWLua mPlayerPackages.insert(mLocalPackages.begin(), mLocalPackages.end()); LuaUtil::LuaStorage::initLuaBindings(mLua.sol()); - mGlobalScripts.addPackage( - "openmw.storage", LuaUtil::LuaStorage::initGlobalPackage(mLua.sol(), &mGlobalStorage)); + mGlobalScripts.addPackage("openmw.storage", LuaUtil::LuaStorage::initGlobalPackage(mLua, &mGlobalStorage)); mMenuScripts.addPackage( - "openmw.storage", LuaUtil::LuaStorage::initMenuPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage)); - mLocalPackages["openmw.storage"] = LuaUtil::LuaStorage::initLocalPackage(mLua.sol(), &mGlobalStorage); + "openmw.storage", LuaUtil::LuaStorage::initMenuPackage(mLua, &mGlobalStorage, &mPlayerStorage)); + mLocalPackages["openmw.storage"] = LuaUtil::LuaStorage::initLocalPackage(mLua, &mGlobalStorage); mPlayerPackages["openmw.storage"] - = LuaUtil::LuaStorage::initPlayerPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage); + = LuaUtil::LuaStorage::initPlayerPackage(mLua, &mGlobalStorage, &mPlayerStorage); mPlayerStorage.setActive(true); mGlobalStorage.setActive(false); diff --git a/components/lua/storage.cpp b/components/lua/storage.cpp index dd53fdffcb..db81b6e172 100644 --- a/components/lua/storage.cpp +++ b/components/lua/storage.cpp @@ -17,6 +17,15 @@ namespace LuaUtil { LuaStorage::Value LuaStorage::Section::sEmpty; + void LuaStorage::registerLifeTime(LuaUtil::LuaState& luaState, sol::table& res) + { + res["LIFE_TIME"] = LuaUtil::makeStrictReadOnly(luaState.tableFromPairs({ + { "Persistent", Section::LifeTime::Persistent }, + { "GameSession", Section::LifeTime::GameSession }, + { "Temporary", Section::LifeTime::Temporary }, + })); + } + sol::object LuaStorage::Value::getCopy(lua_State* L) const { return deserialize(L, mSerializedValue); @@ -142,7 +151,12 @@ namespace LuaUtil sview["removeOnExit"] = [](const SectionView& section) { if (section.mReadOnly) throw std::runtime_error("Access to storage is read only"); - section.mSection->mPermanent = false; + section.mSection->mLifeTime = Section::Temporary; + }; + sview["setLifeTime"] = [](const SectionView& section, Section::LifeTime lifeTime) { + if (section.mReadOnly) + throw std::runtime_error("Access to storage is read only"); + section.mSection->mLifeTime = lifeTime; }; sview["set"] = [](const SectionView& section, std::string_view key, const sol::object& value) { if (section.mReadOnly) @@ -151,26 +165,33 @@ namespace LuaUtil }; } - sol::table LuaStorage::initGlobalPackage(lua_State* lua, LuaStorage* globalStorage) + sol::table LuaStorage::initGlobalPackage(LuaUtil::LuaState& luaState, LuaStorage* globalStorage) { - sol::table res(lua, sol::create); + sol::table res(luaState.sol(), sol::create); + registerLifeTime(luaState, res); + res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getMutableSection(section); }; res["allGlobalSections"] = [globalStorage]() { return globalStorage->getAllSections(); }; return LuaUtil::makeReadOnly(res); } - sol::table LuaStorage::initLocalPackage(lua_State* lua, LuaStorage* globalStorage) + sol::table LuaStorage::initLocalPackage(LuaUtil::LuaState& luaState, LuaStorage* globalStorage) { - sol::table res(lua, sol::create); + sol::table res(luaState.sol(), sol::create); + registerLifeTime(luaState, res); + res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getReadOnlySection(section); }; return LuaUtil::makeReadOnly(res); } - sol::table LuaStorage::initPlayerPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage) + sol::table LuaStorage::initPlayerPackage( + LuaUtil::LuaState& luaState, LuaStorage* globalStorage, LuaStorage* playerStorage) { - sol::table res(lua, sol::create); + sol::table res(luaState.sol(), sol::create); + registerLifeTime(luaState, res); + res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getReadOnlySection(section); }; res["playerSection"] @@ -179,9 +200,12 @@ namespace LuaUtil return LuaUtil::makeReadOnly(res); } - sol::table LuaStorage::initMenuPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage) + sol::table LuaStorage::initMenuPackage( + LuaUtil::LuaState& luaState, LuaStorage* globalStorage, LuaStorage* playerStorage) { - sol::table res(lua, sol::create); + sol::table res(luaState.sol(), sol::create); + registerLifeTime(luaState, res); + res["playerSection"] = [playerStorage](std::string_view section) { return playerStorage->getMutableSection(section, /*forMenuScripts=*/true); }; @@ -199,7 +223,7 @@ namespace LuaUtil it->second->mCallbacks.clear(); // Note that we don't clear menu callbacks for permanent sections // because starting/loading a game doesn't reset menu scripts. - if (!it->second->mPermanent) + if (it->second->mLifeTime == Section::Temporary) { it->second->mMenuScriptsCallbacks.clear(); it->second->mValues.clear(); @@ -238,7 +262,7 @@ namespace LuaUtil sol::table data(mLua, sol::create); for (const auto& [sectionName, section] : mData) { - if (section->mPermanent && !section->mValues.empty()) + if (section->mLifeTime == Section::Persistent && !section->mValues.empty()) data[sectionName] = section->asTable(); } std::string serializedData = serialize(data); diff --git a/components/lua/storage.hpp b/components/lua/storage.hpp index 75e0e14a16..061a5aace3 100644 --- a/components/lua/storage.hpp +++ b/components/lua/storage.hpp @@ -14,11 +14,13 @@ namespace LuaUtil class LuaStorage { public: - static void initLuaBindings(lua_State*); - static sol::table initGlobalPackage(lua_State* lua, LuaStorage* globalStorage); - static sol::table initLocalPackage(lua_State* lua, LuaStorage* globalStorage); - static sol::table initPlayerPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage); - static sol::table initMenuPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage); + static void initLuaBindings(lua_State* L); + static sol::table initGlobalPackage(LuaUtil::LuaState& luaState, LuaStorage* globalStorage); + static sol::table initLocalPackage(LuaUtil::LuaState& luaState, LuaStorage* globalStorage); + static sol::table initPlayerPackage( + LuaUtil::LuaState& luaState, LuaStorage* globalStorage, LuaStorage* playerStorage); + static sol::table initMenuPackage( + LuaUtil::LuaState& luaState, LuaStorage* globalStorage, LuaStorage* playerStorage); explicit LuaStorage(lua_State* lua) : mLua(lua) @@ -78,6 +80,13 @@ namespace LuaUtil struct Section { + enum LifeTime + { + Persistent, + GameSession, + Temporary + }; + explicit Section(LuaStorage* storage, std::string name) : mStorage(storage) , mSectionName(std::move(name)) @@ -96,7 +105,7 @@ namespace LuaUtil std::vector mCallbacks; std::vector mMenuScriptsCallbacks; // menu callbacks are in a separate vector because we don't // remove them in clear() - bool mPermanent = true; + LifeTime mLifeTime = Persistent; static Value sEmpty; void checkIfActive() const { mStorage->checkIfActive(); } @@ -120,6 +129,7 @@ namespace LuaUtil if (!mActive) throw std::logic_error("Trying to access inactive storage"); } + static void registerLifeTime(LuaUtil::LuaState& luaState, sol::table& res); }; } diff --git a/files/lua_api/openmw/storage.lua b/files/lua_api/openmw/storage.lua index 2335719be8..575b0f83d9 100644 --- a/files/lua_api/openmw/storage.lua +++ b/files/lua_api/openmw/storage.lua @@ -15,6 +15,15 @@ -- end -- end)) +--- Possible @{#LifeTime} values +-- @field [parent=#storage] #LifeTime LIFE_TIME + +--- `storage.LIFE_TIME` +-- @type LifeTime +-- @field #number Persistent "0" Data is stored for the whole game session and remains on disk after quitting the game +-- @field #number GameSession "1" Data is stored for the whole game session +-- @field #number Temporary "2" Data is stored until script context reset + --- -- Get a section of the global storage; can be used by any script, but only global scripts can change values. -- Menu scripts can only access it when a game is running. @@ -83,12 +92,28 @@ -- @param #table values (optional) New values --- --- Make the whole section temporary: will be removed on exit or when load a save. +-- (DEPRECATED, use `setLifeTime(openmw.storage.LIFE_TIME.Temporary)`) Make the whole section temporary: will be removed on exit or when load a save. -- Temporary sections have the same interface to get/set values, the only difference is they will not -- be saved to the permanent storage on exit. --- This function can not be used for a global storage section from a local script. +-- This function can be used for a global storage section from a global script or for a player storage section from a player or menu script. -- @function [parent=#StorageSection] removeOnExit -- @param self +-- @usage +-- local storage = require('openmw.storage') +-- local myModData = storage.globalSection('MyModExample') +-- myModData:removeOnExit() + +--- +-- Set the life time of given storage section. +-- New sections initially have a Persistent life time. +-- This function can be used for a global storage section from a global script or for a player storage section from a player or menu script. +-- @function [parent=#StorageSection] setLifeTime +-- @param self +-- @param #LifeTime lifeTime Section life time +-- @usage +-- local storage = require('openmw.storage') +-- local myModData = storage.globalSection('MyModExample') +-- myModData:setLifeTime(storage.LIFE_TIME.Temporary) --- -- Set value by a string key; can not be used for global storage from a local script. From 2a4f12b96e197a9fef5fb964b9f2dbfb6a798698 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 27 Feb 2024 08:04:45 +0400 Subject: [PATCH 174/451] Use a new life time API --- files/data/scripts/omw/settings/global.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/settings/global.lua b/files/data/scripts/omw/settings/global.lua index f7356d15c4..15a9614636 100644 --- a/files/data/scripts/omw/settings/global.lua +++ b/files/data/scripts/omw/settings/global.lua @@ -1,7 +1,7 @@ local storage = require('openmw.storage') local common = require('scripts.omw.settings.common') -common.getSection(true, common.groupSectionKey):removeOnExit() +common.getSection(true, common.groupSectionKey):setLifeTime(storage.LIFE_TIME.Temporary) return { interfaceName = 'Settings', From cd118ee2639c120ae7c5acb54d03adbb72a31b67 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 22 Feb 2024 19:29:19 +0100 Subject: [PATCH 175/451] Expose races to Lua --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/birthsignbindings.cpp | 23 +++-- apps/openmw/mwlua/racebindings.cpp | 120 ++++++++++++++++++++++++ apps/openmw/mwlua/racebindings.hpp | 13 +++ apps/openmw/mwlua/types/npc.cpp | 2 + files/lua_api/openmw/types.lua | 29 ++++++ 6 files changed, 182 insertions(+), 7 deletions(-) create mode 100644 apps/openmw/mwlua/racebindings.cpp create mode 100644 apps/openmw/mwlua/racebindings.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index cf9f265730..5fb06881ec 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -64,7 +64,7 @@ add_openmw_dir (mwlua context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings - classbindings itemdata inputprocessor animationbindings birthsignbindings + classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp index e569bc1b8f..95b427eaa4 100644 --- a/apps/openmw/mwlua/birthsignbindings.cpp +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -21,6 +21,10 @@ namespace sol struct is_automagical> : std::false_type { }; + template <> + struct is_automagical : std::false_type + { + }; } namespace MWLua @@ -43,12 +47,19 @@ namespace MWLua signT["texture"] = sol::readonly_property([vfs](const ESM::BirthSign& rec) -> std::string { return Misc::ResourceHelpers::correctTexturePath(rec.mTexture, vfs); }); - signT["spells"] = sol::readonly_property([lua](const ESM::BirthSign& rec) -> sol::table { - sol::table res(lua, sol::create); - for (size_t i = 0; i < rec.mPowers.mList.size(); ++i) - res[i + 1] = rec.mPowers.mList[i].serializeText(); - return res; - }); + signT["spells"] + = sol::readonly_property([](const ESM::BirthSign& rec) -> const ESM::SpellList* { return &rec.mPowers; }); + + auto spellListT = lua.new_usertype("ESM3_SpellList"); + spellListT[sol::meta_function::length] = [](const ESM::SpellList& list) { return list.mList.size(); }; + spellListT[sol::meta_function::index] + = [](const ESM::SpellList& list, size_t index) -> sol::optional { + if (index == 0 || index > list.mList.size()) + return sol::nullopt; + return list.mList[index - 1].serializeText(); // Translate from Lua's 1-based indexing. + }; + spellListT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + spellListT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); return LuaUtil::makeReadOnly(birthSigns); } diff --git a/apps/openmw/mwlua/racebindings.cpp b/apps/openmw/mwlua/racebindings.cpp new file mode 100644 index 0000000000..b5ea3c12bf --- /dev/null +++ b/apps/openmw/mwlua/racebindings.cpp @@ -0,0 +1,120 @@ +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "luamanagerimp.hpp" +#include "racebindings.hpp" +#include "types/types.hpp" + +namespace +{ + struct RaceAttributes + { + const ESM::Race& mRace; + const sol::state_view mLua; + + sol::table getAttribute(ESM::RefId id) const + { + sol::table res(mLua, sol::create); + res["male"] = mRace.mData.getAttribute(id, true); + res["female"] = mRace.mData.getAttribute(id, false); + return LuaUtil::makeReadOnly(res); + } + }; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical> : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; +} + +namespace MWLua +{ + sol::table initRaceRecordBindings(const Context& context) + { + sol::state_view& lua = context.mLua->sol(); + sol::table races(context.mLua->sol(), sol::create); + addRecordFunctionBinding(races, context); + + auto raceT = lua.new_usertype("ESM3_Race"); + raceT[sol::meta_function::to_string] + = [](const ESM::Race& rec) -> std::string { return "ESM3_Race[" + rec.mId.toDebugString() + "]"; }; + raceT["id"] = sol::readonly_property([](const ESM::Race& rec) { return rec.mId.serializeText(); }); + raceT["name"] = sol::readonly_property([](const ESM::Race& rec) -> std::string_view { return rec.mName; }); + raceT["description"] + = sol::readonly_property([](const ESM::Race& rec) -> std::string_view { return rec.mDescription; }); + raceT["spells"] + = sol::readonly_property([lua](const ESM::Race& rec) -> const ESM::SpellList* { return &rec.mPowers; }); + raceT["skills"] = sol::readonly_property([lua](const ESM::Race& rec) -> sol::table { + sol::table res(lua, sol::create); + for (const auto& skillBonus : rec.mData.mBonus) + { + ESM::RefId skill = ESM::Skill::indexToRefId(skillBonus.mSkill); + if (!skill.empty()) + res[skill.serializeText()] = skillBonus.mBonus; + } + return res; + }); + raceT["isPlayable"] = sol::readonly_property( + [](const ESM::Race& rec) -> bool { return rec.mData.mFlags & ESM::Race::Playable; }); + raceT["isBeast"] + = sol::readonly_property([](const ESM::Race& rec) -> bool { return rec.mData.mFlags & ESM::Race::Beast; }); + raceT["height"] = sol::readonly_property([lua](const ESM::Race& rec) -> sol::table { + sol::table res(lua, sol::create); + res["male"] = rec.mData.mMaleHeight; + res["female"] = rec.mData.mFemaleHeight; + return LuaUtil::makeReadOnly(res); + }); + raceT["weight"] = sol::readonly_property([lua](const ESM::Race& rec) -> sol::table { + sol::table res(lua, sol::create); + res["male"] = rec.mData.mMaleWeight; + res["female"] = rec.mData.mFemaleWeight; + return LuaUtil::makeReadOnly(res); + }); + + raceT["attributes"] = sol::readonly_property([lua](const ESM::Race& rec) -> RaceAttributes { + return { rec, lua }; + }); + + auto attributesT = lua.new_usertype("ESM3_RaceAttributes"); + const auto& store = MWBase::Environment::get().getESMStore()->get(); + attributesT[sol::meta_function::index] + = [&](const RaceAttributes& attributes, std::string_view stringId) -> sol::optional { + ESM::RefId id = ESM::RefId::deserializeText(stringId); + if (!store.search(id)) + return sol::nullopt; + return attributes.getAttribute(id); + }; + attributesT[sol::meta_function::pairs] = [&](sol::this_state ts, RaceAttributes& attributes) { + auto iterator = store.begin(); + return sol::as_function( + [iterator, attributes, + &store]() mutable -> std::pair, sol::optional> { + if (iterator != store.end()) + { + ESM::RefId id = iterator->mId; + ++iterator; + return { id.serializeText(), attributes.getAttribute(id) }; + } + return { sol::nullopt, sol::nullopt }; + }); + }; + + return LuaUtil::makeReadOnly(races); + } +} diff --git a/apps/openmw/mwlua/racebindings.hpp b/apps/openmw/mwlua/racebindings.hpp new file mode 100644 index 0000000000..43ba9237c5 --- /dev/null +++ b/apps/openmw/mwlua/racebindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_RACEBINDINGS_H +#define MWLUA_RACEBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initRaceRecordBindings(const Context& context); +} + +#endif // MWLUA_RACEBINDINGS_H diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index d7d459bb81..29147a826b 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -15,6 +15,7 @@ #include "../classbindings.hpp" #include "../localscripts.hpp" +#include "../racebindings.hpp" #include "../stats.hpp" namespace sol @@ -86,6 +87,7 @@ namespace MWLua addActorServicesBindings(record, context); npc["classes"] = initClassRecordBindings(context); + npc["races"] = initRaceRecordBindings(context); // This function is game-specific, in future we should replace it with something more universal. npc["isWerewolf"] = [](const Object& o) { diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 0c51544f64..d085b355c8 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -958,6 +958,35 @@ -- @param #any objectOrRecordId -- @return #NpcRecord +--- @{#Races}: Race data +-- @field [parent=#NPC] #Races races + +--- +-- A read-only list of all @{#RaceRecord}s in the world database. +-- @field [parent=#Races] #list<#RaceRecord> records + +--- +-- Returns a read-only @{#RaceRecord} +-- @function [parent=#Races] record +-- @param #string recordId +-- @return #RaceRecord + +--- +-- Race data record +-- @type RaceRecord +-- @field #string id Race id +-- @field #string name Race name +-- @field #string description Race description +-- @field #map<#string, #number> skills A map of bonus skill points by skill ID +-- @field #list<#string> spells A read-only list containing the ids of all spells inherent to the race +-- @field #bool isPlayable True if the player can pick this race in character generation +-- @field #bool isBeast True if this race is a beast race +-- @field #map<#string, #number> height A read-only table with male and female fields +-- @field #map<#string, #number> weight A read-only table with male and female fields +-- @field #map<#string, #map<#string, #number>> attributes A read-only table of attribute ID to male and female base values +-- @usage -- Get base strength for men +-- strength = types.NPC.races.records[1].attributes.strength.male + --- -- @type NpcRecord -- @field #string id The record ID of the NPC From f346295975e29c2c6640878e4a048ad4158e385c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 27 Feb 2024 21:57:01 +0100 Subject: [PATCH 176/451] Add a number-per-sex type --- files/lua_api/openmw/types.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index d085b355c8..60f3e79628 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -981,12 +981,17 @@ -- @field #list<#string> spells A read-only list containing the ids of all spells inherent to the race -- @field #bool isPlayable True if the player can pick this race in character generation -- @field #bool isBeast True if this race is a beast race --- @field #map<#string, #number> height A read-only table with male and female fields --- @field #map<#string, #number> weight A read-only table with male and female fields --- @field #map<#string, #map<#string, #number>> attributes A read-only table of attribute ID to male and female base values +-- @field #GenderedNumber height Height values +-- @field #GenderedNumber weight Weight values +-- @field #map<#string, #GenderedNumber> attributes A read-only table of attribute ID to base value -- @usage -- Get base strength for men -- strength = types.NPC.races.records[1].attributes.strength.male +--- +-- @type GenderedNumber +-- @field #number male Male value +-- @field #number female Female value + --- -- @type NpcRecord -- @field #string id The record ID of the NPC From 27b1434f5b7e9fc40bc5cd8ebeac411994d4eaf5 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 28 Feb 2024 01:06:42 +0300 Subject: [PATCH 177/451] Use string_view for full help text --- apps/openmw/mwgui/tooltips.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 938d4176f2..0e0c56c194 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -410,13 +410,13 @@ namespace MWGui const std::string& image = info.icon; int imageSize = (!image.empty()) ? info.imageSize : 0; std::string text = info.text; - std::string extra = info.extra; + std::string_view extra = info.extra; // remove the first newline (easier this way) if (!text.empty() && text[0] == '\n') text.erase(0, 1); if (!extra.empty() && extra[0] == '\n') - extra.erase(0, 1); + extra = extra.substr(1); const ESM::Enchantment* enchant = nullptr; const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); From 322a378907b034abfd722a861c7ab04f929ea0fb Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 28 Feb 2024 00:49:15 +0000 Subject: [PATCH 178/451] Load correct config files in the wizard --- apps/wizard/main.cpp | 2 +- apps/wizard/mainwizard.cpp | 8 +++----- apps/wizard/mainwizard.hpp | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/wizard/main.cpp b/apps/wizard/main.cpp index b62428f946..e740f06015 100644 --- a/apps/wizard/main.cpp +++ b/apps/wizard/main.cpp @@ -47,7 +47,7 @@ int main(int argc, char* argv[]) l10n::installQtTranslations(app, "wizard", resourcesPath); - Wizard::MainWizard wizard; + Wizard::MainWizard wizard(std::move(configurationManager)); wizard.show(); return app.exec(); diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 2f1f373cfd..e8bd6f7007 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -26,9 +26,10 @@ using namespace Process; -Wizard::MainWizard::MainWizard(QWidget* parent) +Wizard::MainWizard::MainWizard(Files::ConfigurationManager&& cfgMgr, QWidget* parent) : QWizard(parent) , mInstallations() + , mCfgMgr(cfgMgr) , mError(false) , mGameSettings(mCfgMgr) { @@ -172,10 +173,7 @@ void Wizard::MainWizard::setupGameSettings() file.close(); // Now the rest - QStringList paths; - paths.append(Files::getUserConfigPathQString(mCfgMgr)); - paths.append(QLatin1String("openmw.cfg")); - paths.append(Files::getGlobalConfigPathQString(mCfgMgr)); + QStringList paths = Files::getActiveConfigPathsQString(mCfgMgr); for (const QString& path2 : paths) { diff --git a/apps/wizard/mainwizard.hpp b/apps/wizard/mainwizard.hpp index 60f46fa1a5..2f120f1955 100644 --- a/apps/wizard/mainwizard.hpp +++ b/apps/wizard/mainwizard.hpp @@ -45,7 +45,7 @@ namespace Wizard Page_Conclusion }; - MainWizard(QWidget* parent = nullptr); + MainWizard(Files::ConfigurationManager&& cfgMgr, QWidget* parent = nullptr); ~MainWizard() override; bool findFiles(const QString& name, const QString& path); From d111b4bbd9bd8570ab8524a72dab044585eea911 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 28 Feb 2024 00:58:30 +0000 Subject: [PATCH 179/451] Handle built-in content files in content model There's also handling for files declared as originating from a lower-priority openmw.cfg, e.g. anything in the local config or any intermediate ones, as they can't be disabled or reordered. There's no way to mark such files yet, but the logic's the same as built-in files, so everything will be fine once that's set up. --- .../contentselector/model/contentmodel.cpp | 38 +++++++++++++++---- components/contentselector/model/esmfile.cpp | 27 +++++++++++++ components/contentselector/model/esmfile.hpp | 19 +++++++++- 3 files changed, 75 insertions(+), 9 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 377edc3b1c..003f2ee241 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -109,6 +109,9 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index if (!file) return Qt::NoItemFlags; + if (file->builtIn() || file->fromAnotherConfigFile()) + return Qt::NoItemFlags; + // game files can always be checked if (file == mGameFile) return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; @@ -130,7 +133,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index continue; noGameFiles = false; - if (mCheckedFiles.contains(depFile)) + if (depFile->builtIn() || depFile->fromAnotherConfigFile() || mCheckedFiles.contains(depFile)) { gamefileChecked = true; break; @@ -217,14 +220,14 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int if (file == mGameFile) return QVariant(); - return mCheckedFiles.contains(file) ? Qt::Checked : Qt::Unchecked; + return (file->builtIn() || file->fromAnotherConfigFile() || mCheckedFiles.contains(file)) ? Qt::Checked : Qt::Unchecked; } case Qt::UserRole: { if (file == mGameFile) return ContentType_GameFile; - else if (flags(index)) + else return ContentType_Addon; break; @@ -279,7 +282,12 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const { int checkValue = value.toInt(); bool setState = false; - if (checkValue == Qt::Checked && !mCheckedFiles.contains(file)) + if (file->builtIn() || file->fromAnotherConfigFile()) + { + setState = false; + success = false; + } + else if (checkValue == Qt::Checked && !mCheckedFiles.contains(file)) { setState = true; success = true; @@ -374,6 +382,13 @@ bool ContentSelectorModel::ContentModel::dropMimeData( else if (parent.isValid()) beginRow = parent.row(); + int firstModifiable = 0; + while (item(firstModifiable)->builtIn() || item(firstModifiable)->fromAnotherConfigFile()) + ++firstModifiable; + + if (beginRow < firstModifiable) + return false; + QByteArray encodedData = data->data(mMimeType); QDataStream stream(&encodedData, QIODevice::ReadOnly); @@ -449,6 +464,9 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf file->setGameFiles({}); } + if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0) + file->setBuiltIn(true); + if (info.fileName().endsWith(".omwscripts", Qt::CaseInsensitive)) { file->setDate(info.lastModified()); @@ -579,15 +597,20 @@ void ContentSelectorModel::ContentModel::setCurrentGameFile(const EsmFile* file) void ContentSelectorModel::ContentModel::sortFiles() { emit layoutAboutToBeChanged(); + + int firstModifiable = 0; + while (mFiles.at(firstModifiable)->builtIn() || mFiles.at(firstModifiable)->fromAnotherConfigFile()) + ++firstModifiable; + // Dependency sort std::unordered_set moved; - for (int i = mFiles.size() - 1; i > 0;) + for (int i = mFiles.size() - 1; i > firstModifiable;) { const auto file = mFiles.at(i); if (moved.find(file) == moved.end()) { int index = -1; - for (int j = 0; j < i; ++j) + for (int j = firstModifiable; j < i; ++j) { const QStringList& gameFiles = mFiles.at(j)->gameFiles(); // All addon files are implicitly dependent on the game file @@ -650,6 +673,7 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList& fileL { if (setCheckState(filepath, true)) { + // setCheckState already gracefully handles builtIn and fromAnotherConfigFile // as necessary, move plug-ins in visible list to match sequence of supplied filelist const EsmFile* file = item(filepath); int filePosition = indexFromItem(file).row(); @@ -747,7 +771,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, const EsmFile* file = item(filepath); - if (!file) + if (!file || file->builtIn() || file->fromAnotherConfigFile()) return false; if (checkState) diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index e4280baef7..02ac0efa70 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -41,6 +41,16 @@ void ContentSelectorModel::EsmFile::setDescription(const QString& description) mDescription = description; } +void ContentSelectorModel::EsmFile::setBuiltIn(bool builtIn) +{ + mBuiltIn = builtIn; +} + +void ContentSelectorModel::EsmFile::setFromAnotherConfigFile(bool fromAnotherConfigFile) +{ + mFromAnotherConfigFile = fromAnotherConfigFile; +} + bool ContentSelectorModel::EsmFile::isGameFile() const { return (mGameFiles.size() == 0) @@ -76,6 +86,14 @@ QVariant ContentSelectorModel::EsmFile::fileProperty(const FileProperty prop) co return mDescription; break; + case FileProperty_BuiltIn: + return mBuiltIn; + break; + + case FileProperty_FromAnotherConfigFile: + return mFromAnotherConfigFile; + break; + case FileProperty_GameFile: return mGameFiles; break; @@ -113,6 +131,15 @@ void ContentSelectorModel::EsmFile::setFileProperty(const FileProperty prop, con mDescription = value; break; + // todo: check these work + case FileProperty_BuiltIn: + mBuiltIn = value == "true"; + break; + + case FileProperty_FromAnotherConfigFile: + mFromAnotherConfigFile = value == "true"; + break; + case FileProperty_GameFile: mGameFiles << value; break; diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index 606cc3d319..42b6cfeff6 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -26,7 +26,9 @@ namespace ContentSelectorModel FileProperty_DateModified = 3, FileProperty_FilePath = 4, FileProperty_Description = 5, - FileProperty_GameFile = 6 + FileProperty_BuiltIn = 6, + FileProperty_FromAnotherConfigFile = 7, + FileProperty_GameFile = 8, }; EsmFile(const QString& fileName = QString(), ModelItem* parent = nullptr); @@ -40,6 +42,8 @@ namespace ContentSelectorModel void setFilePath(const QString& path); void setGameFiles(const QStringList& gameFiles); void setDescription(const QString& description); + void setBuiltIn(bool builtIn); + void setFromAnotherConfigFile(bool fromAnotherConfigFile); void addGameFile(const QString& name) { mGameFiles.append(name); } QVariant fileProperty(const FileProperty prop) const; @@ -49,18 +53,27 @@ namespace ContentSelectorModel QDateTime modified() const { return mModified; } QString formatVersion() const { return mVersion; } QString filePath() const { return mPath; } + bool builtIn() const { return mBuiltIn; } + bool fromAnotherConfigFile() const { return mFromAnotherConfigFile; } /// @note Contains file names, not paths. const QStringList& gameFiles() const { return mGameFiles; } QString description() const { return mDescription; } QString toolTip() const { - return mTooltipTemlate.arg(mAuthor) + QString tooltip = mTooltipTemlate.arg(mAuthor) .arg(mVersion) .arg(mModified.toString(Qt::ISODate)) .arg(mPath) .arg(mDescription) .arg(mGameFiles.join(", ")); + + if (mBuiltIn) + tooltip += tr("
This content file cannot be disabled because it is part of OpenMW.
"); + else if (mFromAnotherConfigFile) + tooltip += tr("
This content file cannot be disabled because it is enabled in a config file other than the user one.
"); + + return tooltip; } bool isGameFile() const; @@ -82,6 +95,8 @@ namespace ContentSelectorModel QStringList mGameFiles; QString mDescription; QString mToolTip; + bool mBuiltIn = false; + bool mFromAnotherConfigFile = false; }; } From 0519e1215f608fa82374a220ebefa182f25f05db Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 28 Feb 2024 17:20:46 +0100 Subject: [PATCH 180/451] Unify the creation of RefId tables --- apps/openmw/mwlua/birthsignbindings.cpp | 17 +++--------- apps/openmw/mwlua/classbindings.cpp | 30 +++++----------------- apps/openmw/mwlua/factionbindings.cpp | 21 +++------------ apps/openmw/mwlua/idcollectionbindings.hpp | 25 ++++++++++++++++++ apps/openmw/mwlua/racebindings.cpp | 5 ++-- 5 files changed, 41 insertions(+), 57 deletions(-) create mode 100644 apps/openmw/mwlua/idcollectionbindings.hpp diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp index 95b427eaa4..f9266991ae 100644 --- a/apps/openmw/mwlua/birthsignbindings.cpp +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -8,6 +8,7 @@ #include "../mwworld/esmstore.hpp" #include "birthsignbindings.hpp" +#include "idcollectionbindings.hpp" #include "luamanagerimp.hpp" #include "types/types.hpp" @@ -47,19 +48,9 @@ namespace MWLua signT["texture"] = sol::readonly_property([vfs](const ESM::BirthSign& rec) -> std::string { return Misc::ResourceHelpers::correctTexturePath(rec.mTexture, vfs); }); - signT["spells"] - = sol::readonly_property([](const ESM::BirthSign& rec) -> const ESM::SpellList* { return &rec.mPowers; }); - - auto spellListT = lua.new_usertype("ESM3_SpellList"); - spellListT[sol::meta_function::length] = [](const ESM::SpellList& list) { return list.mList.size(); }; - spellListT[sol::meta_function::index] - = [](const ESM::SpellList& list, size_t index) -> sol::optional { - if (index == 0 || index > list.mList.size()) - return sol::nullopt; - return list.mList[index - 1].serializeText(); // Translate from Lua's 1-based indexing. - }; - spellListT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - spellListT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + signT["spells"] = sol::readonly_property([lua](const ESM::BirthSign& rec) -> sol::table { + return createReadOnlyRefIdTable(lua, rec.mPowers.mList); + }); return LuaUtil::makeReadOnly(birthSigns); } diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index ea1ea8e7ef..a272a5b407 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -6,6 +6,7 @@ #include "../mwworld/esmstore.hpp" #include "classbindings.hpp" +#include "idcollectionbindings.hpp" #include "luamanagerimp.hpp" #include "stats.hpp" #include "types/types.hpp" @@ -40,34 +41,15 @@ namespace MWLua = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { return rec.mDescription; }); classT["attributes"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { - sol::table res(lua, sol::create); - auto attribute = rec.mData.mAttribute; - for (size_t i = 0; i < attribute.size(); ++i) - { - ESM::RefId attributeId = ESM::Attribute::indexToRefId(attribute[i]); - res[i + 1] = attributeId.serializeText(); - } - return res; + return createReadOnlyRefIdTable(lua, rec.mData.mAttribute, ESM::Attribute::indexToRefId); }); classT["majorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { - sol::table res(lua, sol::create); - auto skills = rec.mData.mSkills; - for (size_t i = 0; i < skills.size(); ++i) - { - ESM::RefId skillId = ESM::Skill::indexToRefId(skills[i][1]); - res[i + 1] = skillId.serializeText(); - } - return res; + return createReadOnlyRefIdTable( + lua, rec.mData.mSkills, [](const auto& pair) { return ESM::Skill::indexToRefId(pair[1]); }); }); classT["minorSkills"] = sol::readonly_property([lua](const ESM::Class& rec) -> sol::table { - sol::table res(lua, sol::create); - auto skills = rec.mData.mSkills; - for (size_t i = 0; i < skills.size(); ++i) - { - ESM::RefId skillId = ESM::Skill::indexToRefId(skills[i][0]); - res[i + 1] = skillId.serializeText(); - } - return res; + return createReadOnlyRefIdTable( + lua, rec.mData.mSkills, [](const auto& pair) { return ESM::Skill::indexToRefId(pair[0]); }); }); classT["specialization"] = sol::readonly_property([](const ESM::Class& rec) -> std::string_view { diff --git a/apps/openmw/mwlua/factionbindings.cpp b/apps/openmw/mwlua/factionbindings.cpp index 87ce6ced39..e4c65386bf 100644 --- a/apps/openmw/mwlua/factionbindings.cpp +++ b/apps/openmw/mwlua/factionbindings.cpp @@ -9,6 +9,7 @@ #include "../mwmechanics/npcstats.hpp" +#include "idcollectionbindings.hpp" #include "luamanagerimp.hpp" namespace @@ -96,26 +97,10 @@ namespace MWLua return res; }); factionT["attributes"] = sol::readonly_property([&lua](const ESM::Faction& rec) { - sol::table res(lua, sol::create); - for (auto attributeIndex : rec.mData.mAttribute) - { - ESM::RefId id = ESM::Attribute::indexToRefId(attributeIndex); - if (!id.empty()) - res.add(id.serializeText()); - } - - return res; + return createReadOnlyRefIdTable(lua, rec.mData.mAttribute, ESM::Attribute::indexToRefId); }); factionT["skills"] = sol::readonly_property([&lua](const ESM::Faction& rec) { - sol::table res(lua, sol::create); - for (auto skillIndex : rec.mData.mSkills) - { - ESM::RefId id = ESM::Skill::indexToRefId(skillIndex); - if (!id.empty()) - res.add(id.serializeText()); - } - - return res; + return createReadOnlyRefIdTable(lua, rec.mData.mSkills, ESM::Skill::indexToRefId); }); auto rankT = lua.new_usertype("ESM3_FactionRank"); rankT[sol::meta_function::to_string] = [](const FactionRank& rec) -> std::string { diff --git a/apps/openmw/mwlua/idcollectionbindings.hpp b/apps/openmw/mwlua/idcollectionbindings.hpp new file mode 100644 index 0000000000..15e2b14fb9 --- /dev/null +++ b/apps/openmw/mwlua/idcollectionbindings.hpp @@ -0,0 +1,25 @@ +#ifndef MWLUA_IDCOLLECTIONBINDINGS_H +#define MWLUA_IDCOLLECTIONBINDINGS_H + +#include + +#include +#include + +namespace MWLua +{ + template + sol::table createReadOnlyRefIdTable(const sol::state_view& lua, const C& container, P projection = {}) + { + sol::table res(lua, sol::create); + for (const auto& element : container) + { + ESM::RefId id = projection(element); + if (!id.empty()) + res.add(id.serializeText()); + } + return LuaUtil::makeReadOnly(res); + } +} + +#endif diff --git a/apps/openmw/mwlua/racebindings.cpp b/apps/openmw/mwlua/racebindings.cpp index b5ea3c12bf..4ee2f7b5f7 100644 --- a/apps/openmw/mwlua/racebindings.cpp +++ b/apps/openmw/mwlua/racebindings.cpp @@ -6,6 +6,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" +#include "idcollectionbindings.hpp" #include "luamanagerimp.hpp" #include "racebindings.hpp" #include "types/types.hpp" @@ -58,8 +59,8 @@ namespace MWLua raceT["name"] = sol::readonly_property([](const ESM::Race& rec) -> std::string_view { return rec.mName; }); raceT["description"] = sol::readonly_property([](const ESM::Race& rec) -> std::string_view { return rec.mDescription; }); - raceT["spells"] - = sol::readonly_property([lua](const ESM::Race& rec) -> const ESM::SpellList* { return &rec.mPowers; }); + raceT["spells"] = sol::readonly_property( + [lua](const ESM::Race& rec) -> sol::table { return createReadOnlyRefIdTable(lua, rec.mPowers.mList); }); raceT["skills"] = sol::readonly_property([lua](const ESM::Race& rec) -> sol::table { sol::table res(lua, sol::create); for (const auto& skillBonus : rec.mData.mBonus) From 9e1334cc097b6765f42e7943d98fe1f0a69540df Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 28 Feb 2024 23:49:55 +0000 Subject: [PATCH 181/451] Resync composing and path openmw.cfg settings with options.cpp --- components/config/gamesettings.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index f8ff3d362c..8ddf72325f 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -108,9 +108,10 @@ bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap Date: Thu, 29 Feb 2024 00:48:03 +0000 Subject: [PATCH 183/451] Tooltips for data-local and resources/vfs --- apps/launcher/datafilespage.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index a0287afd87..de72aa2577 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -295,7 +295,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) // add new achives files presents in current directory addArchivesFromDir(currentDir); - QString tooltip; + QStringList tooltip; // add content files presents in current directory mSelector->addFiles(currentDir, mNewDataDirs.contains(canonicalDirPath)); @@ -308,7 +308,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) // Display new content with custom formatting if (mNewDataDirs.contains(canonicalDirPath)) { - tooltip += tr("Will be added to the current profile"); + tooltip << tr("Will be added to the current profile"); QFont font = item->font(); font.setBold(true); font.setItalic(true); @@ -321,15 +321,17 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) auto flags = item->flags(); item->setFlags(flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled)); } + if (currentDir == mDataLocal) + tooltip << tr("This is the data-local directory and cannot be disabled"); + else if (currentDir == resourcesVfs) + tooltip << tr("This directory is part of OpenMW and cannot be disabled"); // Add a "data file" icon if the directory contains a content file if (mSelector->containsDataFiles(currentDir)) { item->setIcon(QIcon(":/images/openmw-plugin.png")); - if (!tooltip.isEmpty()) - tooltip += "\n"; - tooltip += tr("Contains content file(s)"); + tooltip << tr("Contains content file(s)"); } else { @@ -339,7 +341,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) auto emptyIcon = QIcon(pixmap); item->setIcon(emptyIcon); } - item->setToolTip(tooltip); + item->setToolTip(tooltip.join('\n')); } mSelector->sortFiles(); From e54decc830fde07cdf95192e25f7689bdea34eee Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 1 Mar 2024 12:24:36 +0100 Subject: [PATCH 184/451] Remove redundant is_automagicals --- apps/openmw/mwlua/birthsignbindings.cpp | 8 -------- apps/openmw/mwlua/classbindings.cpp | 4 ---- apps/openmw/mwlua/racebindings.cpp | 4 ---- 3 files changed, 16 deletions(-) diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp index f9266991ae..6993ae0105 100644 --- a/apps/openmw/mwlua/birthsignbindings.cpp +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -18,14 +18,6 @@ namespace sol struct is_automagical : std::false_type { }; - template <> - struct is_automagical> : std::false_type - { - }; - template <> - struct is_automagical : std::false_type - { - }; } namespace MWLua diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index a272a5b407..c9d5a9fb7b 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -17,10 +17,6 @@ namespace sol struct is_automagical : std::false_type { }; - template <> - struct is_automagical> : std::false_type - { - }; } namespace MWLua diff --git a/apps/openmw/mwlua/racebindings.cpp b/apps/openmw/mwlua/racebindings.cpp index 4ee2f7b5f7..e2e2ae2a8a 100644 --- a/apps/openmw/mwlua/racebindings.cpp +++ b/apps/openmw/mwlua/racebindings.cpp @@ -35,10 +35,6 @@ namespace sol { }; template <> - struct is_automagical> : std::false_type - { - }; - template <> struct is_automagical : std::false_type { }; From 958f70736f838a4c92d1ab93d9c617ec6ca79dd1 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 2 Mar 2024 12:45:48 +0100 Subject: [PATCH 185/451] Implement auto calculated potion values --- CHANGELOG.md | 1 + apps/esmtool/labels.cpp | 14 +++++++++++++ apps/esmtool/labels.hpp | 1 + apps/esmtool/record.cpp | 2 +- apps/opencs/model/world/refidadapterimp.cpp | 4 ++-- apps/openmw/mwclass/potion.cpp | 7 +++---- apps/openmw/mwmechanics/alchemy.cpp | 4 ++-- apps/openmw/mwmechanics/spellutil.cpp | 22 +++++++++++++++++++-- apps/openmw/mwmechanics/spellutil.hpp | 4 ++++ components/esm3/loadalch.cpp | 4 ++-- components/esm3/loadalch.hpp | 7 ++++++- 11 files changed, 56 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c03b91d364..fe081224ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -154,6 +154,7 @@ Bug #7832: Ingredient tooltips show magnitude for Fortify Maximum Magicka effect Bug #7840: First run of the launcher doesn't save viewing distance as the default value Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs + Bug #7859: AutoCalc flag is not used to calculate potion value Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index d0a443de53..6def8b4a42 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -1,5 +1,6 @@ #include "labels.hpp" +#include #include #include #include @@ -987,3 +988,16 @@ std::string recordFlags(uint32_t flags) properties += Misc::StringUtils::format("(0x%08X)", flags); return properties; } + +std::string potionFlags(int flags) +{ + std::string properties; + if (flags == 0) + properties += "[None] "; + if (flags & ESM::Potion::Autocalc) + properties += "Autocalc "; + if (flags & (0xFFFFFFFF ^ ESM::Enchantment::Autocalc)) + properties += "Invalid "; + properties += Misc::StringUtils::format("(0x%08X)", flags); + return properties; +} diff --git a/apps/esmtool/labels.hpp b/apps/esmtool/labels.hpp index df6d419ca3..c3a78141b4 100644 --- a/apps/esmtool/labels.hpp +++ b/apps/esmtool/labels.hpp @@ -60,6 +60,7 @@ std::string itemListFlags(int flags); std::string lightFlags(int flags); std::string magicEffectFlags(int flags); std::string npcFlags(int flags); +std::string potionFlags(int flags); std::string raceFlags(int flags); std::string spellFlags(int flags); std::string weaponFlags(int flags); diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index b1185a4d33..d099bdfcfb 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -479,7 +479,7 @@ namespace EsmTool std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; - std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl; + std::cout << " Flags: " << potionFlags(mData.mData.mFlags) << std::endl; printEffectList(mData.mEffects); std::cout << " Deleted: " << mIsDeleted << std::endl; } diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index c6179facb8..bdccab9cda 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -33,7 +33,7 @@ QVariant CSMWorld::PotionRefIdAdapter::getData(const RefIdColumn* column, const data.getRecord(RefIdData::LocalIndex(index, UniversalId::Type_Potion))); if (column == mAutoCalc) - return record.get().mData.mAutoCalc != 0; + return record.get().mData.mFlags & ESM::Potion::Autocalc; // to show nested tables in dialogue subview, see IdTree::hasChildren() if (column == mColumns.mEffects) @@ -51,7 +51,7 @@ void CSMWorld::PotionRefIdAdapter::setData( ESM::Potion potion = record.get(); if (column == mAutoCalc) - potion.mData.mAutoCalc = value.toInt(); + potion.mData.mFlags = value.toBool(); else { InventoryRefIdAdapter::setData(column, data, index, value); diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 42c122cb48..0d98302fe6 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -19,6 +19,7 @@ #include "../mwrender/renderinginterface.hpp" #include "../mwmechanics/alchemy.hpp" +#include "../mwmechanics/spellutil.hpp" #include "classmodel.hpp" @@ -65,9 +66,7 @@ namespace MWClass int Potion::getValue(const MWWorld::ConstPtr& ptr) const { - const MWWorld::LiveCellRef* ref = ptr.get(); - - return ref->mBase->mData.mValue; + return MWMechanics::getPotionValue(*ptr.get()->mBase); } const ESM::RefId& Potion::getUpSoundId(const MWWorld::ConstPtr& ptr) const @@ -101,7 +100,7 @@ namespace MWClass std::string text; text += "\n#{sWeight}: " + MWGui::ToolTips::toString(ref->mBase->mData.mWeight); - text += MWGui::ToolTips::getValueString(ref->mBase->mData.mValue, "#{sValue}"); + text += MWGui::ToolTips::getValueString(getValue(ptr), "#{sValue}"); info.effects = MWGui::Widgets::MWEffectList::effectListFromESM(&ref->mBase->mEffects); diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 5a995e7f06..3adb399483 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -250,7 +250,7 @@ const ESM::Potion* MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co if (iter->mName != toFind.mName || iter->mScript != toFind.mScript || iter->mData.mWeight != toFind.mData.mWeight || iter->mData.mValue != toFind.mData.mValue - || iter->mData.mAutoCalc != toFind.mData.mAutoCalc) + || iter->mData.mFlags != toFind.mData.mFlags) continue; // Don't choose an ID that came from the content files, would have unintended side effects @@ -310,7 +310,7 @@ void MWMechanics::Alchemy::addPotion(const std::string& name) newRecord.mData.mWeight /= countIngredients(); newRecord.mData.mValue = mValue; - newRecord.mData.mAutoCalc = 0; + newRecord.mData.mFlags = 0; newRecord.mRecordFlags = 0; newRecord.mName = name; diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index 2a63a3a444..671939cb00 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -48,7 +49,7 @@ namespace MWMechanics bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; int minMagn = hasMagnitude ? effect.mMagnMin : 1; int maxMagn = hasMagnitude ? effect.mMagnMax : 1; - if (method != EffectCostMethod::GameEnchantment) + if (method == EffectCostMethod::PlayerSpell || method == EffectCostMethod::GameSpell) { minMagn = std::max(1, minMagn); maxMagn = std::max(1, maxMagn); @@ -57,21 +58,28 @@ namespace MWMechanics if (!appliedOnce) duration = std::max(1, duration); static const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); + static const float iAlchemyMod = store.get().find("iAlchemyMod")->mValue.getFloat(); int durationOffset = 0; int minArea = 0; + float costMult = fEffectCostMult; if (method == EffectCostMethod::PlayerSpell) { durationOffset = 1; minArea = 1; } + else if (method == EffectCostMethod::GamePotion) + { + minArea = 1; + costMult = iAlchemyMod; + } float x = 0.5 * (minMagn + maxMagn); x *= 0.1 * magicEffect->mData.mBaseCost; x *= durationOffset + duration; x += 0.05 * std::max(minArea, effect.mArea) * magicEffect->mData.mBaseCost; - return x * fEffectCostMult; + return x * costMult; } int calcSpellCost(const ESM::Spell& spell) @@ -140,6 +148,16 @@ namespace MWMechanics return enchantment.mData.mCharge; } + int getPotionValue(const ESM::Potion& potion) + { + if (potion.mData.mFlags & ESM::Potion::Autocalc) + { + float cost = getTotalCost(potion.mEffects, EffectCostMethod::GamePotion); + return std::round(cost); + } + return potion.mData.mValue; + } + float calcSpellBaseSuccessChance(const ESM::Spell* spell, const MWWorld::Ptr& actor, ESM::RefId* effectiveSchool) { // Morrowind for some reason uses a formula slightly different from magicka cost calculation diff --git a/apps/openmw/mwmechanics/spellutil.hpp b/apps/openmw/mwmechanics/spellutil.hpp index a332a231e6..fa9b0c64b9 100644 --- a/apps/openmw/mwmechanics/spellutil.hpp +++ b/apps/openmw/mwmechanics/spellutil.hpp @@ -8,6 +8,7 @@ namespace ESM struct ENAMstruct; struct Enchantment; struct MagicEffect; + struct Potion; struct Spell; } @@ -23,6 +24,7 @@ namespace MWMechanics GameSpell, PlayerSpell, GameEnchantment, + GamePotion, }; float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr, @@ -33,6 +35,8 @@ namespace MWMechanics int getEffectiveEnchantmentCastCost(const ESM::Enchantment& enchantment, const MWWorld::Ptr& actor); int getEnchantmentCharge(const ESM::Enchantment& enchantment); + int getPotionValue(const ESM::Potion& potion); + /** * @param spell spell to cast * @param actor calculate spell success chance for this actor (depends on actor's skills) diff --git a/components/esm3/loadalch.cpp b/components/esm3/loadalch.cpp index 2b01dd9b09..b85bbd558e 100644 --- a/components/esm3/loadalch.cpp +++ b/components/esm3/loadalch.cpp @@ -36,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("ALDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mAutoCalc); + esm.getHT(mData.mWeight, mData.mValue, mData.mFlags); hasData = true; break; case fourCC("ENAM"): @@ -80,7 +80,7 @@ namespace ESM mRecordFlags = 0; mData.mWeight = 0; mData.mValue = 0; - mData.mAutoCalc = 0; + mData.mFlags = 0; mName.clear(); mModel.clear(); mIcon.clear(); diff --git a/components/esm3/loadalch.hpp b/components/esm3/loadalch.hpp index ddecd7e3c7..814d21937b 100644 --- a/components/esm3/loadalch.hpp +++ b/components/esm3/loadalch.hpp @@ -25,11 +25,16 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Potion"; } + enum Flags + { + Autocalc = 1 // Determines value + }; + struct ALDTstruct { float mWeight; int32_t mValue; - int32_t mAutoCalc; + int32_t mFlags; }; ALDTstruct mData; From 7a84f27eeb60c1814884b2b59b328eb02a13e3b7 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 3 Mar 2024 09:57:55 +0300 Subject: [PATCH 186/451] Properly calculate touch spell hit position (#6156) Reorganize hit contact logic and remove dead code (distance checks, melee hit contact-relevant stuff) --- CHANGELOG.md | 1 + apps/openmw/mwworld/worldimp.cpp | 106 ++++++++++++------------------- 2 files changed, 42 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c03b91d364..98526c7c24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Bug #6025: Subrecords cannot overlap records Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex Bug #6146: Lua command `actor:setEquipment` doesn't trigger mwscripts when equipping or unequipping a scripted item + Bug #6156: 1ft Charm or Sound magic effect vfx doesn't work properly Bug #6190: Unintuitive sun specularity time of day dependence Bug #6222: global map cell size can crash openmw if set to too high a value Bug #6313: Followers with high Fight can turn hostile diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index cbef6789f1..8cafb1dbe0 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2939,89 +2939,65 @@ namespace MWWorld { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit - // result. - std::vector targetActors; - if (!actor.isEmpty() && actor != MWMechanics::getPlayer() && !manualSpell) - stats.getAiSequence().getCombatTargets(targetActors); - - const float fCombatDistance = mStore.get().find("fCombatDistance")->mValue.getFloat(); - - osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); - - // for player we can take faced object first MWWorld::Ptr target; - if (actor == MWMechanics::getPlayer()) - target = getFacedObject(); - - // if the faced object can not be activated, do not use it - if (!target.isEmpty() && !target.getClass().hasToolTip(target)) - target = nullptr; - - if (target.isEmpty()) + // For scripted spells we should not use hit contact + if (manualSpell) { - // For scripted spells we should not use hit contact - if (manualSpell) + if (actor != MWMechanics::getPlayer()) { - if (actor != MWMechanics::getPlayer()) + for (const auto& package : stats.getAiSequence()) { - for (const auto& package : stats.getAiSequence()) + if (package->getTypeId() == MWMechanics::AiPackageTypeId::Cast) { - if (package->getTypeId() == MWMechanics::AiPackageTypeId::Cast) - { - target = package->getTarget(); - break; - } + target = package->getTarget(); + break; } } } - else + } + else + { + if (actor == MWMechanics::getPlayer()) + target = getFacedObject(); + + if (target.isEmpty() || !target.getClass().hasToolTip(target)) { // For actor targets, we want to use melee hit contact. // This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would - // be very hard to aim at otherwise. For object targets, we want the detailed shapes (rendering - // raycast). If we used the bounding boxes for static objects, then we would not be able to target e.g. + // be very hard to aim at otherwise. + // For object targets, we want the detailed shapes (rendering raycast). + // If we used the bounding boxes for static objects, then we would not be able to target e.g. // objects lying on a shelf. - const std::pair result1 = MWMechanics::getHitContact(actor, fCombatDistance); - - // Get the target to use for "on touch" effects, using the facing direction from Head node - osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); - - osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1, 0, 0)) - * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0, 0, -1)); - - osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); - float distance = getMaxActivationDistance(); - osg::Vec3f dest = origin + direction * distance; - - MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); - - float dist1 = std::numeric_limits::max(); - float dist2 = std::numeric_limits::max(); + const float fCombatDistance = mStore.get().find("fCombatDistance")->mValue.getFloat(); + target = MWMechanics::getHitContact(actor, fCombatDistance).first; - if (!result1.first.isEmpty() && result1.first.getClass().isActor()) - dist1 = (origin - result1.second).length(); - if (result2.mHit) - dist2 = (origin - result2.mHitPointWorld).length(); - - if (!result1.first.isEmpty() && result1.first.getClass().isActor()) - { - target = result1.first; - hitPosition = result1.second; - if (dist1 > getMaxActivationDistance()) - target = nullptr; - } - else if (result2.mHit) + if (target.isEmpty()) { - target = result2.mHitObject; - hitPosition = result2.mHitPointWorld; - if (dist2 > getMaxActivationDistance() && !target.isEmpty() - && !target.getClass().hasToolTip(target)) - target = nullptr; + // Get the target using the facing direction from Head node + const osg::Vec3f origin = getActorHeadTransform(actor).getTrans(); + const osg::Quat orient = osg::Quat(actor.getRefData().getPosition().rot[0], osg::Vec3f(-1, 0, 0)) + * osg::Quat(actor.getRefData().getPosition().rot[2], osg::Vec3f(0, 0, -1)); + const osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); + const osg::Vec3f dest = origin + direction * getMaxActivationDistance(); + const MWRender::RenderingManager::RayResult result = mRendering->castRay(origin, dest, true, true); + if (result.mHit) + target = result.mHitObject; } } } + osg::Vec3f hitPosition = actor.getRefData().getPosition().asVec3(); + if (!target.isEmpty()) + { + // Touch explosion placement doesn't depend on where the target was "touched". + // For NPC targets, it also doesn't depend on the height. + // Using 0.75 of the collision box height seems accurate for actors and looks decent for non-actors. + // In Morrowind, touch explosion placement for non-actors is inexplicable, + // often putting the explosion way above the object. + hitPosition = target.getRefData().getPosition().asVec3(); + hitPosition.z() += mPhysics->getHalfExtents(target).z() * 2.f * Constants::TorsoHeight; + } + const ESM::RefId& selectedSpell = stats.getSpells().getSelectedSpell(); MWMechanics::CastSpell cast(actor, target, false, manualSpell); From cef59e8928c955875b4610783b2db7539fba5c67 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 27 Feb 2024 20:47:46 +0100 Subject: [PATCH 187/451] Replace fixed size writeHNT calls with decomposition --- apps/esmtool/record.cpp | 4 +- .../model/world/nestedcoladapterimp.cpp | 1 - apps/opencs/view/render/object.hpp | 2 +- apps/openmw/mwmechanics/pathfinding.hpp | 2 +- apps/openmw/mwworld/refdata.hpp | 2 +- .../detournavigator/objecttransform.hpp | 2 +- components/esm/defs.hpp | 37 +-------------- components/esm/position.hpp | 47 +++++++++++++++++++ components/esm3/cellid.cpp | 10 +++- components/esm3/cellref.cpp | 8 ++-- components/esm3/cellref.hpp | 5 +- components/esm3/esmreader.hpp | 10 ++++ components/esm3/loadalch.cpp | 12 ++++- components/esm3/loadappa.cpp | 12 ++++- components/esm3/loadarmo.cpp | 11 ++++- components/esm3/loadbody.cpp | 12 ++++- components/esm3/loadbook.cpp | 12 ++++- components/esm3/loadcell.cpp | 19 ++++++-- components/esm3/loadclas.cpp | 15 ++++-- components/esm3/loadclot.cpp | 12 ++++- components/esm3/loadcont.cpp | 4 +- components/esm3/loadcrea.cpp | 12 +++-- components/esm3/loadench.cpp | 12 ++++- components/esm3/loadinfo.cpp | 14 ++++-- components/esm3/loadinfo.hpp | 3 +- components/esm3/loadingr.cpp | 12 ++++- components/esm3/loadligh.cpp | 12 ++++- components/esm3/loadlock.cpp | 12 ++++- components/esm3/loadmisc.cpp | 12 ++++- components/esm3/loadpgrd.cpp | 34 ++++++++------ components/esm3/loadpgrd.hpp | 2 - components/esm3/loadprob.cpp | 12 ++++- components/esm3/loadrepa.cpp | 12 ++++- components/esm3/loadskil.cpp | 11 ++++- components/esm3/loadsndg.cpp | 2 +- components/esm3/loadsoun.cpp | 12 ++++- components/esm3/loadspel.cpp | 12 ++++- components/esm3/objectstate.cpp | 7 +-- components/esm3/objectstate.hpp | 5 +- components/esm3/player.cpp | 4 +- components/esm3/player.hpp | 6 +-- components/esm3/savedgame.cpp | 17 +++++-- components/esm3/transport.cpp | 9 ++-- components/esm3/transport.hpp | 2 +- components/esm4/loadachr.hpp | 1 + components/esm4/loadrefr.hpp | 1 + components/esm4/reference.hpp | 2 +- components/misc/convert.hpp | 2 +- components/resource/foreachbulletobject.hpp | 2 +- 49 files changed, 339 insertions(+), 144 deletions(-) create mode 100644 components/esm/position.hpp diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index d099bdfcfb..912ad0d683 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -839,8 +839,7 @@ namespace EsmTool std::cout << " Quest Status: " << questStatusLabel(mData.mQuestStatus) << " (" << mData.mQuestStatus << ")" << std::endl; - std::cout << " Unknown1: " << mData.mData.mUnknown1 << std::endl; - std::cout << " Unknown2: " << (int)mData.mData.mUnknown2 << std::endl; + std::cout << " Type: " << dialogTypeLabel(mData.mData.mType) << std::endl; for (const ESM::DialInfo::SelectStruct& rule : mData.mSelects) std::cout << " Select Rule: " << ruleString(rule) << std::endl; @@ -1137,7 +1136,6 @@ namespace EsmTool std::cout << " Coordinates: (" << point.mX << "," << point.mY << "," << point.mZ << ")" << std::endl; std::cout << " Auto-Generated: " << (int)point.mAutogenerated << std::endl; std::cout << " Connections: " << (int)point.mConnectionNum << std::endl; - std::cout << " Unknown: " << point.mUnknown << std::endl; i++; } diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 8b8c7b17be..34205e9421 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -38,7 +38,6 @@ namespace CSMWorld point.mZ = 0; point.mAutogenerated = 0; point.mConnectionNum = 0; - point.mUnknown = 0; points.insert(points.begin() + position, point); pathgrid.mData.mPoints = pathgrid.mPoints.size(); diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 5c73b12211..31f0d93ac4 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include "tagbase.hpp" diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 44566d3b45..0f688686cd 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include namespace MWWorld diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index ae80a0d64e..1b57971f11 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -1,7 +1,7 @@ #ifndef GAME_MWWORLD_REFDATA_H #define GAME_MWWORLD_REFDATA_H -#include +#include #include #include diff --git a/components/detournavigator/objecttransform.hpp b/components/detournavigator/objecttransform.hpp index e56f5dd392..1d52817f52 100644 --- a/components/detournavigator/objecttransform.hpp +++ b/components/detournavigator/objecttransform.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTTRANSFORM_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_OBJECTTRANSFORM_H -#include +#include #include diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index cbc70582c0..52b2afb8de 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -4,12 +4,8 @@ #include #include -#include - -#include - -#include "components/esm/fourcc.hpp" #include +#include #include namespace ESM @@ -33,37 +29,6 @@ namespace ESM RT_Target = 2 }; - // Position and rotation - struct Position - { - float pos[3]{}; - - // In radians - float rot[3]{}; - - osg::Vec3f asVec3() const { return osg::Vec3f(pos[0], pos[1], pos[2]); } - - osg::Vec3f asRotationVec3() const { return osg::Vec3f(rot[0], rot[1], rot[2]); } - - friend inline bool operator<(const Position& l, const Position& r) - { - const auto tuple = [](const Position& v) { return std::tuple(v.asVec3(), v.asRotationVec3()); }; - return tuple(l) < tuple(r); - } - }; - - bool inline operator==(const Position& left, const Position& right) noexcept - { - return left.pos[0] == right.pos[0] && left.pos[1] == right.pos[1] && left.pos[2] == right.pos[2] - && left.rot[0] == right.rot[0] && left.rot[1] == right.rot[1] && left.rot[2] == right.rot[2]; - } - - bool inline operator!=(const Position& left, const Position& right) noexcept - { - return left.pos[0] != right.pos[0] || left.pos[1] != right.pos[1] || left.pos[2] != right.pos[2] - || left.rot[0] != right.rot[0] || left.rot[1] != right.rot[1] || left.rot[2] != right.rot[2]; - } - constexpr unsigned int sEsm4RecnameFlag = 0x00800000; constexpr unsigned int esm3Recname(const char (&name)[5]) diff --git a/components/esm/position.hpp b/components/esm/position.hpp new file mode 100644 index 0000000000..d48997610e --- /dev/null +++ b/components/esm/position.hpp @@ -0,0 +1,47 @@ +#ifndef OPENMW_ESM3_POSITION_H +#define OPENMW_ESM3_POSITION_H + +#include +#include +#include + +namespace ESM +{ + // Position and rotation + struct Position + { + float pos[3]{}; + + // In radians + float rot[3]{}; + + osg::Vec3f asVec3() const { return osg::Vec3f(pos[0], pos[1], pos[2]); } + + osg::Vec3f asRotationVec3() const { return osg::Vec3f(rot[0], rot[1], rot[2]); } + + friend inline bool operator<(const Position& l, const Position& r) + { + const auto tuple = [](const Position& v) { return std::tuple(v.asVec3(), v.asRotationVec3()); }; + return tuple(l) < tuple(r); + } + }; + + bool inline operator==(const Position& left, const Position& right) noexcept + { + return left.pos[0] == right.pos[0] && left.pos[1] == right.pos[1] && left.pos[2] == right.pos[2] + && left.rot[0] == right.rot[0] && left.rot[1] == right.rot[1] && left.rot[2] == right.rot[2]; + } + + bool inline operator!=(const Position& left, const Position& right) noexcept + { + return left.pos[0] != right.pos[0] || left.pos[1] != right.pos[1] || left.pos[2] != right.pos[2] + || left.rot[0] != right.rot[0] || left.rot[1] != right.rot[1] || left.rot[2] != right.rot[2]; + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.pos, v.rot); + } +} +#endif \ No newline at end of file diff --git a/components/esm3/cellid.cpp b/components/esm3/cellid.cpp index 9a5be3aada..4d08691034 100644 --- a/components/esm3/cellid.cpp +++ b/components/esm3/cellid.cpp @@ -3,9 +3,15 @@ #include "esmreader.hpp" #include "esmwriter.hpp" #include +#include namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mX, v.mY); + } void CellId::load(ESMReader& esm) { @@ -13,7 +19,7 @@ namespace ESM mIndex.mX = 0; mIndex.mY = 0; - mPaged = esm.getHNOT("CIDX", mIndex.mX, mIndex.mY); + mPaged = esm.getOptionalComposite("CIDX", mIndex); } void CellId::save(ESMWriter& esm) const @@ -21,7 +27,7 @@ namespace ESM esm.writeHNString("SPAC", mWorldspace); if (mPaged) - esm.writeHNT("CIDX", mIndex, 8); + esm.writeNamedComposite("CIDX", mIndex); } struct VisitCellRefId diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index 93a2ece669..ecba6f7f5e 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -112,7 +112,7 @@ namespace ESM case fourCC("DODT"): if constexpr (load) { - esm.getHT(cellRef.mDoorDest.pos, cellRef.mDoorDest.rot); + esm.getSubComposite(cellRef.mDoorDest); cellRef.mTeleport = true; } else @@ -132,7 +132,7 @@ namespace ESM break; case fourCC("DATA"): if constexpr (load) - esm.getHT(cellRef.mPos.pos, cellRef.mPos.rot); + esm.getSubComposite(cellRef.mPos); else esm.skipHSub(); break; @@ -224,7 +224,7 @@ namespace ESM if (!inInventory && mTeleport) { - esm.writeHNT("DODT", mDoorDest); + esm.writeNamedComposite("DODT", mDoorDest); esm.writeHNOCString("DNAM", mDestCell); } @@ -243,7 +243,7 @@ namespace ESM esm.writeHNT("UNAM", mReferenceBlocked); if (!inInventory) - esm.writeHNT("DATA", mPos, 24); + esm.writeNamedComposite("DATA", mPos); } void CellRef::blank() diff --git a/components/esm3/cellref.hpp b/components/esm3/cellref.hpp index 84b6ae1d18..5079095889 100644 --- a/components/esm3/cellref.hpp +++ b/components/esm3/cellref.hpp @@ -4,8 +4,9 @@ #include #include -#include "components/esm/defs.hpp" -#include "components/esm/refid.hpp" +#include +#include +#include namespace ESM { diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 4af2264828..7d0b9b980c 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -184,6 +184,16 @@ namespace ESM decompose(value, [&](auto&... args) { getHNT(name, args...); }); } + bool getOptionalComposite(NAME name, auto& value) + { + if (isNextSub(name)) + { + getSubComposite(value); + return true; + } + return false; + } + void getComposite(auto& value) { decompose(value, [&](auto&... args) { (getT(args), ...); }); diff --git a/components/esm3/loadalch.cpp b/components/esm3/loadalch.cpp index b85bbd558e..4e6c2ad1e2 100644 --- a/components/esm3/loadalch.cpp +++ b/components/esm3/loadalch.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mFlags); + } + void Potion::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -36,7 +44,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("ALDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("ENAM"): @@ -71,7 +79,7 @@ namespace ESM esm.writeHNOCString("TEXT", mIcon); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("ALDT", mData, 12); + esm.writeNamedComposite("ALDT", mData); mEffects.save(esm); } diff --git a/components/esm3/loadappa.cpp b/components/esm3/loadappa.cpp index ecc00222b8..40d9fc3f72 100644 --- a/components/esm3/loadappa.cpp +++ b/components/esm3/loadappa.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mQuality, v.mWeight, v.mValue); + } + void Apparatus::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("AADT"): - esm.getHT(mData.mType, mData.mQuality, mData.mWeight, mData.mValue); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -65,7 +73,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNCString("FNAM", mName); - esm.writeHNT("AADT", mData, 16); + esm.writeNamedComposite("AADT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNCString("ITEX", mIcon); } diff --git a/components/esm3/loadarmo.cpp b/components/esm3/loadarmo.cpp index 1832014173..37290ae39a 100644 --- a/components/esm3/loadarmo.cpp +++ b/components/esm3/loadarmo.cpp @@ -3,8 +3,15 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mWeight, v.mValue, v.mHealth, v.mEnchant, v.mArmor); + } void PartReferenceList::add(ESMReader& esm) { @@ -59,7 +66,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("AODT"): - esm.getHT(mData.mType, mData.mWeight, mData.mValue, mData.mHealth, mData.mEnchant, mData.mArmor); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -103,7 +110,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCRefId("SCRI", mScript); - esm.writeHNT("AODT", mData, 24); + esm.writeNamedComposite("AODT", mData); esm.writeHNOCString("ITEX", mIcon); mParts.save(esm); esm.writeHNOCRefId("ENAM", mEnchant); diff --git a/components/esm3/loadbody.cpp b/components/esm3/loadbody.cpp index 066e5ec949..8c944c6da0 100644 --- a/components/esm3/loadbody.cpp +++ b/components/esm3/loadbody.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mPart, v.mVampire, v.mFlags, v.mType); + } + void BodyPart::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mRace = esm.getRefId(); break; case fourCC("BYDT"): - esm.getHT(mData.mPart, mData.mVampire, mData.mFlags, mData.mType); + esm.getSubComposite(mData); hasData = true; break; case SREC_DELE: @@ -59,7 +67,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCRefId("FNAM", mRace); - esm.writeHNT("BYDT", mData, 4); + esm.writeNamedComposite("BYDT", mData); } void BodyPart::blank() diff --git a/components/esm3/loadbook.cpp b/components/esm3/loadbook.cpp index 8083c59828..bece59d31b 100644 --- a/components/esm3/loadbook.cpp +++ b/components/esm3/loadbook.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mIsScroll, v.mSkillId, v.mEnchant); + } + void Book::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("BKDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mIsScroll, mData.mSkillId, mData.mEnchant); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -70,7 +78,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("BKDT", mData, 20); + esm.writeNamedComposite("BKDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); esm.writeHNOString("TEXT", mText); diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 0c37e64f1e..3c651fac1a 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include "esmreader.hpp" @@ -41,6 +42,18 @@ namespace ESM { const StringRefId Cell::sDefaultWorldspaceId = StringRefId("sys::default"); + template T> + void decompose(T&& v, const auto& f) + { + f(v.mFlags, v.mX, v.mY); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mAmbient, v.mSunlight, v.mFog, v.mFogDensity); + } + // Some overloaded compare operators. bool operator==(const MovedCellRef& ref, const RefNum& refNum) { @@ -93,7 +106,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("DATA"): - esm.getHT(mData.mFlags, mData.mX, mData.mY); + esm.getSubComposite(mData); hasData = true; break; case SREC_DELE: @@ -181,7 +194,7 @@ namespace ESM void Cell::save(ESMWriter& esm, bool isDeleted) const { esm.writeHNCString("NAME", mName); - esm.writeHNT("DATA", mData, 12); + esm.writeNamedComposite("DATA", mData); if (isDeleted) { @@ -199,7 +212,7 @@ namespace ESM if (mData.mFlags & QuasiEx) esm.writeHNOCRefId("RGNN", mRegion); else if (mHasAmbi) - esm.writeHNT("AMBI", mAmbi, 16); + esm.writeNamedComposite("AMBI", mAmbi); } else { diff --git a/components/esm3/loadclas.cpp b/components/esm3/loadclas.cpp index ec4ff680fa..1fd22e2a49 100644 --- a/components/esm3/loadclas.cpp +++ b/components/esm3/loadclas.cpp @@ -2,7 +2,9 @@ #include -#include "components/esm/defs.hpp" +#include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" @@ -12,6 +14,12 @@ namespace ESM = { "sSpecializationCombat", "sSpecializationMagic", "sSpecializationStealth" }; const std::array Class::specializationIndexToLuaId = { "combat", "magic", "stealth" }; + template T> + void decompose(T&& v, const auto& f) + { + f(v.mAttribute, v.mSpecialization, v.mSkills, v.mIsPlayable, v.mServices); + } + int32_t& Class::CLDTstruct::getSkill(int index, bool major) { return mSkills.at(index)[major ? 1 : 0]; @@ -42,8 +50,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("CLDT"): - esm.getHT( - mData.mAttribute, mData.mSpecialization, mData.mSkills, mData.mIsPlayable, mData.mServices); + esm.getSubComposite(mData); if (mData.mIsPlayable > 1) esm.fail("Unknown bool value"); hasData = true; @@ -77,7 +84,7 @@ namespace ESM } esm.writeHNOCString("FNAM", mName); - esm.writeHNT("CLDT", mData, 60); + esm.writeNamedComposite("CLDT", mData); esm.writeHNOString("DESC", mDescription); } diff --git a/components/esm3/loadclot.cpp b/components/esm3/loadclot.cpp index 7d60c82197..8e778243fc 100644 --- a/components/esm3/loadclot.cpp +++ b/components/esm3/loadclot.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mWeight, v.mValue, v.mEnchant); + } + void Clothing::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -30,7 +38,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("CTDT"): - esm.getHT(mData.mType, mData.mWeight, mData.mValue, mData.mEnchant); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -73,7 +81,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("CTDT", mData, 12); + esm.writeNamedComposite("CTDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); diff --git a/components/esm3/loadcont.cpp b/components/esm3/loadcont.cpp index d016654fea..9d90491448 100644 --- a/components/esm3/loadcont.cpp +++ b/components/esm3/loadcont.cpp @@ -100,8 +100,8 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("CNDT", mWeight, 4); - esm.writeHNT("FLAG", mFlags, 4); + esm.writeHNT("CNDT", mWeight); + esm.writeHNT("FLAG", mFlags); esm.writeHNOCRefId("SCRI", mScript); diff --git a/components/esm3/loadcrea.cpp b/components/esm3/loadcrea.cpp index 5a0d8048bc..83bdbd06ad 100644 --- a/components/esm3/loadcrea.cpp +++ b/components/esm3/loadcrea.cpp @@ -1,12 +1,19 @@ #include "loadcrea.hpp" #include +#include #include "esmreader.hpp" #include "esmwriter.hpp" namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mLevel, v.mAttributes, v.mHealth, v.mMana, v.mFatigue, v.mSoul, v.mCombat, v.mMagic, v.mStealth, + v.mAttack, v.mGold); + } void Creature::load(ESMReader& esm, bool& isDeleted) { @@ -48,8 +55,7 @@ namespace ESM mScript = esm.getRefId(); break; case fourCC("NPDT"): - esm.getHT(mData.mType, mData.mLevel, mData.mAttributes, mData.mHealth, mData.mMana, mData.mFatigue, - mData.mSoul, mData.mCombat, mData.mMagic, mData.mStealth, mData.mAttack, mData.mGold); + esm.getSubComposite(mData); hasNpdt = true; break; case fourCC("FLAG"): @@ -121,7 +127,7 @@ namespace ESM esm.writeHNOCRefId("CNAM", mOriginal); esm.writeHNOCString("FNAM", mName); esm.writeHNOCRefId("SCRI", mScript); - esm.writeHNT("NPDT", mData, 96); + esm.writeNamedComposite("NPDT", mData); esm.writeHNT("FLAG", ((mBloodType << 10) + mFlags)); if (mScale != 1.0) { diff --git a/components/esm3/loadench.cpp b/components/esm3/loadench.cpp index 1d19b690f0..9eb4fae301 100644 --- a/components/esm3/loadench.cpp +++ b/components/esm3/loadench.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mCost, v.mCharge, v.mFlags); + } + void Enchantment::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -23,7 +31,7 @@ namespace ESM hasName = true; break; case fourCC("ENDT"): - esm.getHT(mData.mType, mData.mCost, mData.mCharge, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("ENAM"): @@ -55,7 +63,7 @@ namespace ESM return; } - esm.writeHNT("ENDT", mData, 16); + esm.writeNamedComposite("ENDT", mData); mEffects.save(esm); } diff --git a/components/esm3/loadinfo.cpp b/components/esm3/loadinfo.cpp index 9cff21da3e..714d59fef4 100644 --- a/components/esm3/loadinfo.cpp +++ b/components/esm3/loadinfo.cpp @@ -3,8 +3,17 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + char padding = 0; + f(v.mType, v.mDisposition, v.mRank, v.mGender, v.mPCrank, padding); + } + void DialInfo::load(ESMReader& esm, bool& isDeleted) { mId = esm.getHNRefId("INAM"); @@ -23,8 +32,7 @@ namespace ESM switch (esm.retSubName().toInt()) { case fourCC("DATA"): - esm.getHT(mData.mUnknown1, mData.mDisposition, mData.mRank, mData.mGender, mData.mPCrank, - mData.mUnknown2); + esm.getSubComposite(mData); break; case fourCC("ONAM"): mActor = esm.getRefId(); @@ -102,7 +110,7 @@ namespace ESM return; } - esm.writeHNT("DATA", mData, 12); + esm.writeNamedComposite("DATA", mData); esm.writeHNOCRefId("ONAM", mActor); esm.writeHNOCRefId("RNAM", mRace); esm.writeHNOCRefId("CNAM", mClass); diff --git a/components/esm3/loadinfo.hpp b/components/esm3/loadinfo.hpp index 518e2eaa54..c2756e8d9c 100644 --- a/components/esm3/loadinfo.hpp +++ b/components/esm3/loadinfo.hpp @@ -35,7 +35,7 @@ namespace ESM struct DATAstruct { - int32_t mUnknown1 = 0; + int32_t mType = 0; // See Dialogue::Type union { int32_t mDisposition = 0; // Used for dialogue responses @@ -44,7 +44,6 @@ namespace ESM signed char mRank = -1; // Rank of NPC signed char mGender = Gender::NA; // See Gender enum signed char mPCrank = -1; // Player rank - signed char mUnknown2 = 0; }; // 12 bytes DATAstruct mData; diff --git a/components/esm3/loadingr.cpp b/components/esm3/loadingr.cpp index 4e409ab63d..6a4753d8e4 100644 --- a/components/esm3/loadingr.cpp +++ b/components/esm3/loadingr.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mEffectID, v.mSkills, v.mAttributes); + } + void Ingredient::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("IRDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mEffectID, mData.mSkills, mData.mAttributes); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -82,7 +90,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("IRDT", mData, 56); + esm.writeNamedComposite("IRDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadligh.cpp b/components/esm3/loadligh.cpp index e22f6110c2..bb4f6bac7b 100644 --- a/components/esm3/loadligh.cpp +++ b/components/esm3/loadligh.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mTime, v.mRadius, v.mColor, v.mFlags); + } + void Light::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -31,7 +39,7 @@ namespace ESM mIcon = esm.getHString(); break; case fourCC("LHDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mTime, mData.mRadius, mData.mColor, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -68,7 +76,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("ITEX", mIcon); - esm.writeHNT("LHDT", mData, 24); + esm.writeNamedComposite("LHDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCRefId("SNAM", mSound); } diff --git a/components/esm3/loadlock.cpp b/components/esm3/loadlock.cpp index 578a8a36a7..019d6f9952 100644 --- a/components/esm3/loadlock.cpp +++ b/components/esm3/loadlock.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mQuality, v.mUses); + } + void Lockpick::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("LKDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mQuality, mData.mUses); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -66,7 +74,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("LKDT", mData, 16); + esm.writeNamedComposite("LKDT", mData); esm.writeHNORefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadmisc.cpp b/components/esm3/loadmisc.cpp index b38ce63294..63df1c6551 100644 --- a/components/esm3/loadmisc.cpp +++ b/components/esm3/loadmisc.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mFlags); + } + void Miscellaneous::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("MCDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -65,7 +73,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("MCDT", mData, 12); + esm.writeNamedComposite("MCDT", mData); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadpgrd.cpp b/components/esm3/loadpgrd.cpp index 4f0a62a9d4..ebd51dcff0 100644 --- a/components/esm3/loadpgrd.cpp +++ b/components/esm3/loadpgrd.cpp @@ -3,8 +3,23 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mX, v.mY, v.mGranularity, v.mPoints); + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding[2] = { 0, 0 }; + f(v.mX, v.mY, v.mZ, v.mAutogenerated, v.mConnectionNum, padding); + } + Pathgrid::Point& Pathgrid::Point::operator=(const float rhs[3]) { mX = static_cast(rhs[0]); @@ -12,7 +27,6 @@ namespace ESM mZ = static_cast(rhs[2]); mAutogenerated = 0; mConnectionNum = 0; - mUnknown = 0; return *this; } Pathgrid::Point::Point(const float rhs[3]) @@ -21,7 +35,6 @@ namespace ESM , mZ(static_cast(rhs[2])) , mAutogenerated(0) , mConnectionNum(0) - , mUnknown(0) { } Pathgrid::Point::Point() @@ -30,7 +43,6 @@ namespace ESM , mZ(0) , mAutogenerated(0) , mConnectionNum(0) - , mUnknown(0) { } @@ -54,15 +66,14 @@ namespace ESM mCell = esm.getRefId(); break; case fourCC("DATA"): - esm.getHT(mData.mX, mData.mY, mData.mGranularity, mData.mPoints); + esm.getSubComposite(mData); hasData = true; break; case fourCC("PGRP"): { esm.getSubHeader(); uint32_t size = esm.getSubSize(); - // Check that the sizes match up. Size = 16 * path points - if (size != sizeof(Point) * mData.mPoints) + if (size != 16u * mData.mPoints) esm.fail("Path point subrecord size mismatch"); else { @@ -70,12 +81,7 @@ namespace ESM for (uint16_t i = 0; i < mData.mPoints; ++i) { Point p; - esm.getT(p.mX); - esm.getT(p.mY); - esm.getT(p.mZ); - esm.getT(p.mAutogenerated); - esm.getT(p.mConnectionNum); - esm.getT(p.mUnknown); + esm.getComposite(p); mPoints.push_back(p); edgeCount += p.mConnectionNum; } @@ -160,7 +166,7 @@ namespace ESM // Save esm.writeHNCRefId("NAME", mCell); - esm.writeHNT("DATA", mData, 12); + esm.writeNamedComposite("DATA", mData); if (isDeleted) { @@ -173,7 +179,7 @@ namespace ESM esm.startSubRecord("PGRP"); for (const Point& point : correctedPoints) { - esm.writeT(point); + esm.writeComposite(point); } esm.endRecord("PGRP"); } diff --git a/components/esm3/loadpgrd.hpp b/components/esm3/loadpgrd.hpp index a343552efb..f2a33f9b9a 100644 --- a/components/esm3/loadpgrd.hpp +++ b/components/esm3/loadpgrd.hpp @@ -35,7 +35,6 @@ namespace ESM int32_t mX, mY, mZ; // Location of point unsigned char mAutogenerated; // autogenerated vs. user coloring flag? unsigned char mConnectionNum; // number of connections for this point - int16_t mUnknown; Point& operator=(const float[3]); Point(const float[3]); Point(); @@ -45,7 +44,6 @@ namespace ESM , mZ(z) , mAutogenerated(0) , mConnectionNum(0) - , mUnknown(0) { } }; // 16 bytes diff --git a/components/esm3/loadprob.cpp b/components/esm3/loadprob.cpp index 3f9ba95bf1..5e3086c7b9 100644 --- a/components/esm3/loadprob.cpp +++ b/components/esm3/loadprob.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mQuality, v.mUses); + } + void Probe::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("PBDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mQuality, mData.mUses); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -66,7 +74,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("PBDT", mData, 16); + esm.writeNamedComposite("PBDT", mData); esm.writeHNORefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadrepa.cpp b/components/esm3/loadrepa.cpp index c911cb1a23..886072ab56 100644 --- a/components/esm3/loadrepa.cpp +++ b/components/esm3/loadrepa.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mWeight, v.mValue, v.mUses, v.mQuality); + } + void Repair::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -28,7 +36,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("RIDT"): - esm.getHT(mData.mWeight, mData.mValue, mData.mUses, mData.mQuality); + esm.getSubComposite(mData); hasData = true; break; case fourCC("SCRI"): @@ -66,7 +74,7 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeHNT("RIDT", mData, 16); + esm.writeNamedComposite("RIDT", mData); esm.writeHNORefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } diff --git a/components/esm3/loadskil.cpp b/components/esm3/loadskil.cpp index fd53726f90..28ea3eadba 100644 --- a/components/esm3/loadskil.cpp +++ b/components/esm3/loadskil.cpp @@ -3,6 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include #include #include @@ -37,6 +38,12 @@ namespace ESM const SkillId Skill::Speechcraft("Speechcraft"); const SkillId Skill::HandToHand("HandToHand"); + template T> + void decompose(T&& v, const auto& f) + { + f(v.mAttribute, v.mSpecialization, v.mUseValue); + } + void Skill::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; // Skill record can't be deleted now (may be changed in the future) @@ -55,7 +62,7 @@ namespace ESM hasIndex = true; break; case fourCC("SKDT"): - esm.getHT(mData.mAttribute, mData.mSpecialization, mData.mUseValue); + esm.getSubComposite(mData); hasData = true; break; case fourCC("DESC"): @@ -78,7 +85,7 @@ namespace ESM void Skill::save(ESMWriter& esm, bool /*isDeleted*/) const { esm.writeHNT("INDX", refIdToIndex(mId)); - esm.writeHNT("SKDT", mData, 24); + esm.writeNamedComposite("SKDT", mData); esm.writeHNOString("DESC", mDescription); } diff --git a/components/esm3/loadsndg.cpp b/components/esm3/loadsndg.cpp index 4e2e2aa3f9..12a68b3afe 100644 --- a/components/esm3/loadsndg.cpp +++ b/components/esm3/loadsndg.cpp @@ -57,7 +57,7 @@ namespace ESM return; } - esm.writeHNT("DATA", mType, 4); + esm.writeHNT("DATA", mType); esm.writeHNOCRefId("CNAM", mCreature); esm.writeHNOCRefId("SNAM", mSound); } diff --git a/components/esm3/loadsoun.cpp b/components/esm3/loadsoun.cpp index fd403e3429..6f72a49a60 100644 --- a/components/esm3/loadsoun.cpp +++ b/components/esm3/loadsoun.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mVolume, v.mMinRange, v.mMaxRange); + } + void Sound::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -25,7 +33,7 @@ namespace ESM mSound = esm.getHString(); break; case fourCC("DATA"): - esm.getHT(mData.mVolume, mData.mMinRange, mData.mMaxRange); + esm.getSubComposite(mData); hasData = true; break; case SREC_DELE: @@ -55,7 +63,7 @@ namespace ESM } esm.writeHNOCString("FNAM", mSound); - esm.writeHNT("DATA", mData, 3); + esm.writeNamedComposite("DATA", mData); } void Sound::blank() diff --git a/components/esm3/loadspel.cpp b/components/esm3/loadspel.cpp index e4f63b8219..e40c03d007 100644 --- a/components/esm3/loadspel.cpp +++ b/components/esm3/loadspel.cpp @@ -3,8 +3,16 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mType, v.mCost, v.mFlags); + } + void Spell::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -27,7 +35,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("SPDT"): - esm.getHT(mData.mType, mData.mCost, mData.mFlags); + esm.getSubComposite(mData); hasData = true; break; case fourCC("ENAM"): @@ -60,7 +68,7 @@ namespace ESM } esm.writeHNOCString("FNAM", mName); - esm.writeHNT("SPDT", mData, 12); + esm.writeNamedComposite("SPDT", mData); mEffects.save(esm); } diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index f8905cfaea..f3017e2d0d 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -37,7 +37,7 @@ namespace ESM } mPosition = mRef.mPos; - esm.getHNOT("POS_", mPosition.pos, mPosition.rot); + esm.getOptionalComposite("POS_", mPosition); mFlags = 0; esm.getHNOT(mFlags, "FLAG"); @@ -66,10 +66,7 @@ namespace ESM if (!inInventory && mPosition != mRef.mPos) { - std::array pos; - memcpy(pos.data(), mPosition.pos, sizeof(float) * 3); - memcpy(pos.data() + 3, mPosition.rot, sizeof(float) * 3); - esm.writeHNT("POS_", pos, 24); + esm.writeNamedComposite("POS_", mPosition); } if (mFlags != 0) diff --git a/components/esm3/objectstate.hpp b/components/esm3/objectstate.hpp index b3f7bd3d45..c947adcd97 100644 --- a/components/esm3/objectstate.hpp +++ b/components/esm3/objectstate.hpp @@ -4,8 +4,9 @@ #include #include -#include "components/esm/luascripts.hpp" -#include "components/esm3/formatversion.hpp" +#include +#include +#include #include "animationstate.hpp" #include "cellref.hpp" diff --git a/components/esm3/player.cpp b/components/esm3/player.cpp index fd280bf12e..bf5864ce4c 100644 --- a/components/esm3/player.cpp +++ b/components/esm3/player.cpp @@ -15,7 +15,7 @@ namespace ESM esm.getHNT("LKEP", mLastKnownExteriorPosition); - mHasMark = esm.getHNOT("MARK", mMarkedPosition.pos, mMarkedPosition.rot); + mHasMark = esm.getOptionalComposite("MARK", mMarkedPosition); if (mHasMark) mMarkedCell = esm.getCellId(); @@ -90,7 +90,7 @@ namespace ESM if (mHasMark) { - esm.writeHNT("MARK", mMarkedPosition, 24); + esm.writeNamedComposite("MARK", mMarkedPosition); esm.writeCellId(mMarkedCell); } diff --git a/components/esm3/player.hpp b/components/esm3/player.hpp index 0f76a3b5eb..0cc0c22dc3 100644 --- a/components/esm3/player.hpp +++ b/components/esm3/player.hpp @@ -3,11 +3,11 @@ #include -#include "components/esm/defs.hpp" -#include "npcstate.hpp" +#include +#include -#include "components/esm/attr.hpp" #include "loadskil.hpp" +#include "npcstate.hpp" namespace ESM { diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index 0dc1fb0653..212925b61d 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -3,10 +3,17 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include "../misc/algorithm.hpp" +#include +#include namespace ESM { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mGameHour, v.mDay, v.mMonth, v.mYear); + } + void SavedGame::load(ESMReader& esm) { mPlayerName = esm.getHNString("PLNA"); @@ -19,7 +26,7 @@ namespace ESM mPlayerCellName = esm.getHNRefId("PLCE").toString(); else mPlayerCellName = esm.getHNString("PLCE"); - esm.getHNT("TSTM", mInGameTime.mGameHour, mInGameTime.mDay, mInGameTime.mMonth, mInGameTime.mYear); + esm.getNamedComposite("TSTM", mInGameTime); esm.getHNT(mTimePlayed, "TIME"); mDescription = esm.getHNString("DESC"); @@ -47,12 +54,12 @@ namespace ESM esm.writeHNString("PLCN", mPlayerClassName); esm.writeHNString("PLCE", mPlayerCellName); - esm.writeHNT("TSTM", mInGameTime, 16); + esm.writeNamedComposite("TSTM", mInGameTime); esm.writeHNT("TIME", mTimePlayed); esm.writeHNString("DESC", mDescription); - for (std::vector::const_iterator iter(mContentFiles.begin()); iter != mContentFiles.end(); ++iter) - esm.writeHNString("DEPE", *iter); + for (const std::string& dependency : mContentFiles) + esm.writeHNString("DEPE", dependency); esm.startSubRecord("SCRN"); esm.write(mScreenshot.data(), mScreenshot.size()); diff --git a/components/esm3/transport.cpp b/components/esm3/transport.cpp index 8b131b1b5f..a72cdbbaf8 100644 --- a/components/esm3/transport.cpp +++ b/components/esm3/transport.cpp @@ -13,7 +13,7 @@ namespace ESM if (esm.retSubName().toInt() == fourCC("DODT")) { Dest dodt; - esm.getHExact(&dodt.mPos, 24); + esm.getSubComposite(dodt.mPos); mList.push_back(dodt); } else if (esm.retSubName().toInt() == fourCC("DNAM")) @@ -28,11 +28,10 @@ namespace ESM void Transport::save(ESMWriter& esm) const { - typedef std::vector::const_iterator DestIter; - for (DestIter it = mList.begin(); it != mList.end(); ++it) + for (const Dest& dest : mList) { - esm.writeHNT("DODT", it->mPos, sizeof(it->mPos)); - esm.writeHNOCString("DNAM", it->mCellName); + esm.writeNamedComposite("DODT", dest.mPos); + esm.writeHNOCString("DNAM", dest.mCellName); } } diff --git a/components/esm3/transport.hpp b/components/esm3/transport.hpp index 555504c994..69bc1119c2 100644 --- a/components/esm3/transport.hpp +++ b/components/esm3/transport.hpp @@ -4,7 +4,7 @@ #include #include -#include "components/esm/defs.hpp" +#include namespace ESM { diff --git a/components/esm4/loadachr.hpp b/components/esm4/loadachr.hpp index 8abb47c8bc..526ab4c057 100644 --- a/components/esm4/loadachr.hpp +++ b/components/esm4/loadachr.hpp @@ -30,6 +30,7 @@ #include #include +#include #include #include "reference.hpp" // Placement, EnableParent diff --git a/components/esm4/loadrefr.hpp b/components/esm4/loadrefr.hpp index ec76928827..99826200c9 100644 --- a/components/esm4/loadrefr.hpp +++ b/components/esm4/loadrefr.hpp @@ -32,6 +32,7 @@ #include "reference.hpp" // EnableParent #include +#include #include namespace ESM4 diff --git a/components/esm4/reference.hpp b/components/esm4/reference.hpp index 9d6efdfd82..33e8fa82f3 100644 --- a/components/esm4/reference.hpp +++ b/components/esm4/reference.hpp @@ -30,8 +30,8 @@ #include #include -#include #include +#include namespace ESM4 { diff --git a/components/misc/convert.hpp b/components/misc/convert.hpp index a66fac9125..5d936b5d5f 100644 --- a/components/misc/convert.hpp +++ b/components/misc/convert.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_MISC_CONVERT_H #define OPENMW_COMPONENTS_MISC_CONVERT_H -#include +#include #include #include diff --git a/components/resource/foreachbulletobject.hpp b/components/resource/foreachbulletobject.hpp index fe39a8ed8c..d7e99cf0b5 100644 --- a/components/resource/foreachbulletobject.hpp +++ b/components/resource/foreachbulletobject.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_RESOURCE_FOREACHBULLETOBJECT_H #define OPENMW_COMPONENTS_RESOURCE_FOREACHBULLETOBJECT_H -#include +#include #include #include From 1f629b13689ad040f7eb77859829b905bffc01d6 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 3 Mar 2024 22:06:39 +0300 Subject: [PATCH 188/451] Account for Hrnchamd's research in touch effect hit position calculation --- apps/openmw/mwworld/worldimp.cpp | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 8cafb1dbe0..0468b36a2f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2939,11 +2939,12 @@ namespace MWWorld { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + const bool casterIsPlayer = actor == MWMechanics::getPlayer(); MWWorld::Ptr target; // For scripted spells we should not use hit contact if (manualSpell) { - if (actor != MWMechanics::getPlayer()) + if (!casterIsPlayer) { for (const auto& package : stats.getAiSequence()) { @@ -2957,7 +2958,7 @@ namespace MWWorld } else { - if (actor == MWMechanics::getPlayer()) + if (casterIsPlayer) target = getFacedObject(); if (target.isEmpty() || !target.getClass().hasToolTip(target)) @@ -2990,12 +2991,21 @@ namespace MWWorld if (!target.isEmpty()) { // Touch explosion placement doesn't depend on where the target was "touched". - // For NPC targets, it also doesn't depend on the height. - // Using 0.75 of the collision box height seems accurate for actors and looks decent for non-actors. - // In Morrowind, touch explosion placement for non-actors is inexplicable, - // often putting the explosion way above the object. + // In Morrowind, it's at 0.7 of the actor's AABB height for actors + // or at 0.7 of the player's height for non-actors if the player is the caster + // This is probably meant to prevent the explosion from being too far above on large objects + // but it often puts the explosions way above small objects, so we'll deviate here + // and use the object's bounds when reasonable (it's $CURRENT_YEAR, we can afford that) + // Note collision object origin is intentionally not used hitPosition = target.getRefData().getPosition().asVec3(); - hitPosition.z() += mPhysics->getHalfExtents(target).z() * 2.f * Constants::TorsoHeight; + constexpr float explosionHeight = 0.7f; + float targetHeight = getHalfExtents(target).z() * 2.f; + if (!target.getClass().isActor() && casterIsPlayer) + { + const float playerHeight = getHalfExtents(actor).z() * 2.f; + targetHeight = std::min(targetHeight, playerHeight); + } + hitPosition.z() += targetHeight * explosionHeight; } const ESM::RefId& selectedSpell = stats.getSpells().getSelectedSpell(); From fe3189557f988523b5214524f91e9378a9b5b11e Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 3 Mar 2024 21:59:06 +0000 Subject: [PATCH 189/451] bump macos --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1a8e22ed82..4f5933a8bc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -521,13 +521,13 @@ Ubuntu_GCC_integration_tests_asan: - build/OpenMW-*.dmg - "build/**/*.log" -macOS13_Xcode14_arm64: +macOS13_Xcode15_arm64: extends: .MacOS - image: macos-12-xcode-14 + image: macos-14-xcode-15 tags: - saas-macos-medium-m1 cache: - key: macOS12_Xcode14_arm64.v4 + key: macOS14_Xcode15_arm64.v1 variables: CCACHE_SIZE: 3G From 4d52ab372c8d6f25f207f3dfcf117df37f54d8ca Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 3 Mar 2024 22:22:44 +0000 Subject: [PATCH 190/451] make the name more like the reality --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4f5933a8bc..2bb4193a79 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -521,7 +521,7 @@ Ubuntu_GCC_integration_tests_asan: - build/OpenMW-*.dmg - "build/**/*.log" -macOS13_Xcode15_arm64: +macOS14_Xcode15_arm64: extends: .MacOS image: macos-14-xcode-15 tags: From a130ca57a49d56c6c6125a4cfbc1b969dcccfb4c Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 6 Mar 2024 00:36:13 +0000 Subject: [PATCH 191/451] Track source of settings This one's a biggie. The basic idea's that GameSettings should know: * what the interpreted value of a setting is, so it can actually be used. * what the original value the user put in their config was, so it can be put back when the config's saved. * which path it's processing the openmw.cfg from so relative paths can be resolved correctly. * whether a setting's a user setting that can be modified, or from one of the other openmw.cfg files that can't necessarily be modified. This had fairly wide-reaching implications. The first is that paths are resolved properly in cases where they previously wouldn't have been. Without this commit, if the launcher saw a relative path in an openmw.cfg, it'd be resolved relative to the process' working directory (which we always set to the binary directory for reasons I won't get into). That's not what the engine does, so is bad. It's also not something a user's likely to suspect. This mess is no longer a problem as paths are resolved correctly when they're loaded instead of on demand when they're used by whatever uses them. Another problem was that if paths used slugs like ?userconfig? would be written back to openmw.cfg with the slugs replaced, which defeats the object of using the slugs. This is also fixed. Tracking which settings are user settings and which are in a non-editable openmw.cfg allows the launcher to grey out rows so they can't be edited (which is sensible as they can't be edited on-disk) while still being aware of content files that are provided by non-user data directories etc. This is done in a pretty straightforward way for the data directories and fallback-archives, as those bits of UI are basic, but it's more complicated for content files as that uses a nmodel/view approach and has a lot more moving parts. Thankfully, I'd already implemented that when dealing with builtin.omwscripts, so it just needed wiring up. One more thing of note is that I made the SettingValue struct storable as a QVariant so it could be attached to the UI widgets as userdata, and then I could just grab the original representation and use it instead of needing any complicated mapping from display value to on-disk value. --- apps/launcher/datafilespage.cpp | 150 +++++++++++++----- apps/launcher/datafilespage.hpp | 6 +- apps/launcher/importpage.cpp | 6 +- apps/launcher/maindialog.cpp | 14 +- apps/launcher/settingspage.cpp | 18 +-- apps/wizard/mainwizard.cpp | 28 ++-- components/config/gamesettings.cpp | 143 ++++++++++------- components/config/gamesettings.hpp | 82 +++++++--- components/config/launchersettings.cpp | 46 +++++- .../contentselector/model/contentmodel.cpp | 27 +++- .../contentselector/model/contentmodel.hpp | 3 +- .../contentselector/view/contentselector.cpp | 7 +- .../contentselector/view/contentselector.hpp | 1 + 13 files changed, 367 insertions(+), 164 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index de72aa2577..d87073df02 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -142,7 +142,7 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C ui.setupUi(this); setObjectName("DataFilesPage"); mSelector = new ContentSelectorView::ContentSelector(ui.contentSelectorWidget, /*showOMWScripts=*/true); - const QString encoding = mGameSettings.value("encoding", "win1252"); + const QString encoding = mGameSettings.value("encoding", { "win1252" }).value; mSelector->setEncoding(encoding); QVector> languages = { { "English", tr("English") }, { "French", tr("French") }, @@ -163,11 +163,11 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C connect(ui.directoryInsertButton, &QPushButton::released, this, [this]() { this->addSubdirectories(false); }); connect(ui.directoryUpButton, &QPushButton::released, this, [this]() { this->moveDirectory(-1); }); connect(ui.directoryDownButton, &QPushButton::released, this, [this]() { this->moveDirectory(1); }); - connect(ui.directoryRemoveButton, &QPushButton::released, this, [this]() { this->removeDirectory(); }); + connect(ui.directoryRemoveButton, &QPushButton::released, this, &DataFilesPage::removeDirectory); connect(ui.archiveUpButton, &QPushButton::released, this, [this]() { this->moveArchives(-1); }); connect(ui.archiveDownButton, &QPushButton::released, this, [this]() { this->moveArchives(1); }); - connect( - ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, [this]() { this->sortDirectories(); }); + connect(ui.directoryListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortDirectories); + connect(ui.archiveListWidget->model(), &QAbstractItemModel::rowsMoved, this, &DataFilesPage::sortArchives); buildView(); loadSettings(); @@ -271,39 +271,50 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) ui.archiveListWidget->clear(); ui.directoryListWidget->clear(); - QStringList directories = mLauncherSettings.getDataDirectoryList(contentModelName); - if (directories.isEmpty()) - directories = mGameSettings.getDataDirs(); + QList directories = mGameSettings.getDataDirs(); + QStringList contentModelDirectories = mLauncherSettings.getDataDirectoryList(contentModelName); + if (!contentModelDirectories.isEmpty()) + { + directories.erase(std::remove_if(directories.begin(), directories.end(), + [&](const Config::SettingValue& dir) { return mGameSettings.isUserSetting(dir); }), + directories.end()); + for (const auto& dir : contentModelDirectories) + directories.push_back({ dir }); + } mDataLocal = mGameSettings.getDataLocal(); if (!mDataLocal.isEmpty()) - directories.insert(0, mDataLocal); + directories.insert(0, { mDataLocal }); const auto& resourcesVfs = mGameSettings.getResourcesVfs(); if (!resourcesVfs.isEmpty()) - directories.insert(0, resourcesVfs); + directories.insert(0, { resourcesVfs }); std::unordered_set visitedDirectories; - for (const QString& currentDir : directories) + for (const Config::SettingValue& currentDir : directories) { - // normalize user supplied directories: resolve symlink, convert to native separator, make absolute - const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir)).canonicalPath(); + // normalize user supplied directories: resolve symlink, convert to native separator + const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir.value)).canonicalPath(); if (!visitedDirectories.insert(canonicalDirPath).second) continue; // add new achives files presents in current directory - addArchivesFromDir(currentDir); + addArchivesFromDir(currentDir.value); QStringList tooltip; // add content files presents in current directory - mSelector->addFiles(currentDir, mNewDataDirs.contains(canonicalDirPath)); + mSelector->addFiles(currentDir.value, mNewDataDirs.contains(canonicalDirPath)); // add current directory to list - ui.directoryListWidget->addItem(currentDir); + ui.directoryListWidget->addItem(currentDir.originalRepresentation); auto row = ui.directoryListWidget->count() - 1; auto* item = ui.directoryListWidget->item(row); + item->setData(Qt::UserRole, QVariant::fromValue(currentDir)); + + if (currentDir.value != currentDir.originalRepresentation) + tooltip << tr("Resolved as %1").arg(currentDir.value); // Display new content with custom formatting if (mNewDataDirs.contains(canonicalDirPath)) @@ -316,18 +327,22 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) } // deactivate data-local and resources/vfs: they are always included - if (currentDir == mDataLocal || currentDir == resourcesVfs) + // same for ones from non-user config files + if (currentDir.value == mDataLocal || currentDir.value == resourcesVfs + || !mGameSettings.isUserSetting(currentDir)) { auto flags = item->flags(); item->setFlags(flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled)); + if (currentDir.value == mDataLocal) + tooltip << tr("This is the data-local directory and cannot be disabled"); + else if (currentDir.value == resourcesVfs) + tooltip << tr("This directory is part of OpenMW and cannot be disabled"); + else + tooltip << tr("This directory is enabled in an openmw.cfg other than the user one"); } - if (currentDir == mDataLocal) - tooltip << tr("This is the data-local directory and cannot be disabled"); - else if (currentDir == resourcesVfs) - tooltip << tr("This directory is part of OpenMW and cannot be disabled"); // Add a "data file" icon if the directory contains a content file - if (mSelector->containsDataFiles(currentDir)) + if (mSelector->containsDataFiles(currentDir.value)) { item->setIcon(QIcon(":/images/openmw-plugin.png")); @@ -345,15 +360,22 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) } mSelector->sortFiles(); - QStringList selectedArchives = mLauncherSettings.getArchiveList(contentModelName); - if (selectedArchives.isEmpty()) - selectedArchives = mGameSettings.getArchiveList(); + QList selectedArchives = mGameSettings.getArchiveList(); + QStringList contentModelSelectedArchives = mLauncherSettings.getArchiveList(contentModelName); + if (contentModelSelectedArchives.isEmpty()) + { + selectedArchives.erase(std::remove_if(selectedArchives.begin(), selectedArchives.end(), + [&](const Config::SettingValue& dir) { return mGameSettings.isUserSetting(dir); }), + selectedArchives.end()); + for (const auto& dir : contentModelSelectedArchives) + selectedArchives.push_back({ dir }); + } // sort and tick BSA according to profile int row = 0; for (const auto& archive : selectedArchives) { - const auto match = ui.archiveListWidget->findItems(archive, Qt::MatchExactly); + const auto match = ui.archiveListWidget->findItems(archive.value, Qt::MatchExactly); if (match.isEmpty()) continue; const auto name = match[0]->text(); @@ -361,9 +383,25 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) ui.archiveListWidget->takeItem(oldrow); ui.archiveListWidget->insertItem(row, name); ui.archiveListWidget->item(row)->setCheckState(Qt::Checked); + ui.archiveListWidget->item(row)->setData(Qt::UserRole, QVariant::fromValue(archive)); + if (!mGameSettings.isUserSetting(archive)) + { + auto flags = ui.archiveListWidget->item(row)->flags(); + ui.archiveListWidget->item(row)->setFlags( + flags & ~(Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled)); + ui.archiveListWidget->item(row)->setToolTip( + tr("This archive is enabled in an openmw.cfg other than the user one")); + } row++; } + QStringList nonUserContent; + for (const auto& content : mGameSettings.getContentList()) + { + if (!mGameSettings.isUserSetting(content)) + nonUserContent.push_back(content.value); + } + mSelector->setNonUserContent(nonUserContent); mSelector->setProfileContent(mLauncherSettings.getContentListFiles(contentModelName)); } @@ -391,7 +429,19 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile) { fileNames.append(item->fileName()); } - mLauncherSettings.setContentList(profileName, dirList, selectedArchivePaths(), fileNames); + QStringList dirNames; + for (const auto& dir : dirList) + { + if (mGameSettings.isUserSetting(dir)) + dirNames.push_back(dir.originalRepresentation); + } + QStringList archiveNames; + for (const auto& archive : selectedArchivePaths()) + { + if (mGameSettings.isUserSetting(archive)) + archiveNames.push_back(archive.originalRepresentation); + } + mLauncherSettings.setContentList(profileName, dirNames, archiveNames, fileNames); mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames); QString language(mSelector->languageBox()->currentData().toString()); @@ -400,38 +450,38 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile) if (language == QLatin1String("Polish")) { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1250" }); } else if (language == QLatin1String("Russian")) { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1251" }); } else { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1252" }); } } -QStringList Launcher::DataFilesPage::selectedDirectoriesPaths() const +QList Launcher::DataFilesPage::selectedDirectoriesPaths() const { - QStringList dirList; + QList dirList; for (int i = 0; i < ui.directoryListWidget->count(); ++i) { const QListWidgetItem* item = ui.directoryListWidget->item(i); if (item->flags() & Qt::ItemIsEnabled) - dirList.append(item->text()); + dirList.append(qvariant_cast(item->data(Qt::UserRole))); } return dirList; } -QStringList Launcher::DataFilesPage::selectedArchivePaths() const +QList Launcher::DataFilesPage::selectedArchivePaths() const { - QStringList archiveList; + QList archiveList; for (int i = 0; i < ui.archiveListWidget->count(); ++i) { const QListWidgetItem* item = ui.archiveListWidget->item(i); if (item->checkState() == Qt::Checked) - archiveList.append(item->text()); + archiveList.append(qvariant_cast(item->data(Qt::UserRole))); } return archiveList; } @@ -585,7 +635,20 @@ void Launcher::DataFilesPage::on_cloneProfileAction_triggered() if (profile.isEmpty()) return; - mLauncherSettings.setContentList(profile, selectedDirectoriesPaths(), selectedArchivePaths(), selectedFilePaths()); + const auto& dirList = selectedDirectoriesPaths(); + QStringList dirNames; + for (const auto& dir : dirList) + { + if (mGameSettings.isUserSetting(dir)) + dirNames.push_back(dir.originalRepresentation); + } + QStringList archiveNames; + for (const auto& archive : selectedArchivePaths()) + { + if (mGameSettings.isUserSetting(archive)) + archiveNames.push_back(archive.originalRepresentation); + } + mLauncherSettings.setContentList(profile, dirNames, archiveNames, selectedFilePaths()); addProfile(profile, true); } @@ -704,6 +767,21 @@ void Launcher::DataFilesPage::sortDirectories() } } +void Launcher::DataFilesPage::sortArchives() +{ + // Ensure disabled entries (aka ones from non-user config files) are always at the top. + for (auto i = 1; i < ui.archiveListWidget->count(); ++i) + { + if (!(ui.archiveListWidget->item(i)->flags() & Qt::ItemIsEnabled) + && (ui.archiveListWidget->item(i - 1)->flags() & Qt::ItemIsEnabled)) + { + const auto item = ui.archiveListWidget->takeItem(i); + ui.archiveListWidget->insertItem(i - 1, item); + ui.archiveListWidget->setCurrentRow(i); + } + } +} + void Launcher::DataFilesPage::moveDirectory(int step) { int selectedRow = ui.directoryListWidget->currentRow(); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index e568137e8f..1b92354dab 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -25,6 +25,7 @@ namespace ContentSelectorView namespace Config { class GameSettings; + struct SettingValue; class LauncherSettings; } @@ -73,6 +74,7 @@ namespace Launcher void updateCloneProfileOkButton(const QString& text); void addSubdirectories(bool append); void sortDirectories(); + void sortArchives(); void removeDirectory(); void moveArchives(int step); void moveDirectory(int step); @@ -146,8 +148,8 @@ namespace Launcher * @return the file paths of all selected content files */ QStringList selectedFilePaths() const; - QStringList selectedArchivePaths() const; - QStringList selectedDirectoriesPaths() const; + QList selectedArchivePaths() const; + QList selectedDirectoriesPaths() const; }; } #endif diff --git a/apps/launcher/importpage.cpp b/apps/launcher/importpage.cpp index 44c5867c0d..47075db1bc 100644 --- a/apps/launcher/importpage.cpp +++ b/apps/launcher/importpage.cpp @@ -37,9 +37,9 @@ Launcher::ImportPage::ImportPage(const Files::ConfigurationManager& cfg, Config: // Detect Morrowind configuration files QStringList iniPaths; - for (const QString& path : mGameSettings.getDataDirs()) + for (const auto& path : mGameSettings.getDataDirs()) { - QDir dir(path); + QDir dir(path.value); dir.setPath(dir.canonicalPath()); // Resolve symlinks if (dir.exists(QString("Morrowind.ini"))) @@ -125,7 +125,7 @@ void Launcher::ImportPage::on_importerButton_clicked() arguments.append(QString("--fonts")); arguments.append(QString("--encoding")); - arguments.append(mGameSettings.value(QString("encoding"), QString("win1252"))); + arguments.append(mGameSettings.value(QString("encoding"), { "win1252" }).value); arguments.append(QString("--ini")); arguments.append(settingsComboBox->currentText()); arguments.append(QString("--cfg")); diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index f9d07d54a5..aca8a64e31 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -320,7 +320,7 @@ bool Launcher::MainDialog::setupGameSettings() QFile file; - auto loadFile = [&](const QString& path, bool (Config::GameSettings::*reader)(QTextStream&, bool), + auto loadFile = [&](const QString& path, bool (Config::GameSettings::*reader)(QTextStream&, const QString&, bool), bool ignoreContent = false) -> std::optional { file.setFileName(path); if (file.exists()) @@ -337,7 +337,7 @@ bool Launcher::MainDialog::setupGameSettings() QTextStream stream(&file); Misc::ensureUtf8Encoding(stream); - (mGameSettings.*reader)(stream, ignoreContent); + (mGameSettings.*reader)(stream, QFileInfo(path).dir().path(), ignoreContent); file.close(); return true; } @@ -360,12 +360,12 @@ bool Launcher::MainDialog::setupGameSettings() bool Launcher::MainDialog::setupGameData() { - QStringList dataDirs; + bool foundData = false; // Check if the paths actually contain data files - for (const QString& path3 : mGameSettings.getDataDirs()) + for (const auto& path3 : mGameSettings.getDataDirs()) { - QDir dir(path3); + QDir dir(path3.value); QStringList filters; filters << "*.esp" << "*.esm" @@ -373,10 +373,10 @@ bool Launcher::MainDialog::setupGameData() << "*.omwaddon"; if (!dir.entryList(filters).isEmpty()) - dataDirs.append(path3); + foundData = true; } - if (dataDirs.isEmpty()) + if (!foundData) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Error detecting Morrowind installation")); diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 93a724909e..0df871c90d 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -340,7 +340,7 @@ bool Launcher::SettingsPage::loadSettings() { loadSettingBool(Settings::input().mGrabCursor, *grabCursorCheckBox); - bool skipMenu = mGameSettings.value("skip-menu").toInt() == 1; + bool skipMenu = mGameSettings.value("skip-menu").value.toInt() == 1; if (skipMenu) { skipMenuCheckBox->setCheckState(Qt::Checked); @@ -348,8 +348,8 @@ bool Launcher::SettingsPage::loadSettings() startDefaultCharacterAtLabel->setEnabled(skipMenu); startDefaultCharacterAtField->setEnabled(skipMenu); - startDefaultCharacterAtField->setText(mGameSettings.value("start")); - runScriptAfterStartupField->setText(mGameSettings.value("script-run")); + startDefaultCharacterAtField->setText(mGameSettings.value("start").value); + runScriptAfterStartupField->setText(mGameSettings.value("script-run").value); } return true; } @@ -536,17 +536,17 @@ void Launcher::SettingsPage::saveSettings() saveSettingBool(*grabCursorCheckBox, Settings::input().mGrabCursor); int skipMenu = skipMenuCheckBox->checkState() == Qt::Checked; - if (skipMenu != mGameSettings.value("skip-menu").toInt()) - mGameSettings.setValue("skip-menu", QString::number(skipMenu)); + if (skipMenu != mGameSettings.value("skip-menu").value.toInt()) + mGameSettings.setValue("skip-menu", { QString::number(skipMenu) }); QString startCell = startDefaultCharacterAtField->text(); - if (startCell != mGameSettings.value("start")) + if (startCell != mGameSettings.value("start").value) { - mGameSettings.setValue("start", startCell); + mGameSettings.setValue("start", { startCell }); } QString scriptRun = runScriptAfterStartupField->text(); - if (scriptRun != mGameSettings.value("script-run")) - mGameSettings.setValue("script-run", scriptRun); + if (scriptRun != mGameSettings.value("script-run").value) + mGameSettings.setValue("script-run", { scriptRun }); } } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index e8bd6f7007..0b5cadd979 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -24,6 +24,8 @@ #include "installationpage.hpp" #endif +#include + using namespace Process; Wizard::MainWizard::MainWizard(Files::ConfigurationManager&& cfgMgr, QWidget* parent) @@ -167,7 +169,7 @@ void Wizard::MainWizard::setupGameSettings() QTextStream stream(&file); Misc::ensureUtf8Encoding(stream); - mGameSettings.readUserFile(stream); + mGameSettings.readUserFile(stream, QFileInfo(path).dir().path()); } file.close(); @@ -196,7 +198,7 @@ void Wizard::MainWizard::setupGameSettings() QTextStream stream(&file); Misc::ensureUtf8Encoding(stream); - mGameSettings.readFile(stream); + mGameSettings.readFile(stream, QFileInfo(path2).dir().path()); } file.close(); } @@ -241,11 +243,11 @@ void Wizard::MainWizard::setupLauncherSettings() void Wizard::MainWizard::setupInstallations() { // Check if the paths actually contain a Morrowind installation - for (const QString& path : mGameSettings.getDataDirs()) + for (const auto& path : mGameSettings.getDataDirs()) { - if (findFiles(QLatin1String("Morrowind"), path)) - addInstallation(path); + if (findFiles(QLatin1String("Morrowind"), path.value)) + addInstallation(path.value); } } @@ -332,10 +334,12 @@ void Wizard::MainWizard::addInstallation(const QString& path) mInstallations.insert(QDir::toNativeSeparators(path), install); // Add it to the openmw.cfg too - if (!mGameSettings.getDataDirs().contains(path)) + const auto& dataDirs = mGameSettings.getDataDirs(); + if (std::none_of( + dataDirs.begin(), dataDirs.end(), [&](const Config::SettingValue& dir) { return dir.value == path; })) { - mGameSettings.setMultiValue(QLatin1String("data"), path); - mGameSettings.addDataDir(path); + mGameSettings.setMultiValue(QLatin1String("data"), { path }); + mGameSettings.addDataDir({ path }); } } @@ -394,15 +398,15 @@ void Wizard::MainWizard::writeSettings() if (language == QLatin1String("Polish")) { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1250")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1250" }); } else if (language == QLatin1String("Russian")) { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1251")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1251" }); } else { - mGameSettings.setValue(QLatin1String("encoding"), QLatin1String("win1252")); + mGameSettings.setValue(QLatin1String("encoding"), { "win1252" }); } // Write the installation path so that openmw can find them @@ -410,7 +414,7 @@ void Wizard::MainWizard::writeSettings() // Make sure the installation path is the last data= entry mGameSettings.removeDataDir(path); - mGameSettings.addDataDir(path); + mGameSettings.addDataDir({ path }); QString userPath(Files::pathToQString(mCfgMgr.getUserConfigPath())); QDir dir(userPath); diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 7cace721bf..9aed4656bc 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -13,7 +13,8 @@ const char Config::GameSettings::sDirectoryKey[] = "data"; namespace { - QStringList reverse(QStringList values) + template + QList reverse(QList values) { std::reverse(values.begin(), values.end()); return values; @@ -27,70 +28,69 @@ Config::GameSettings::GameSettings(const Files::ConfigurationManager& cfg) void Config::GameSettings::validatePaths() { - QStringList paths = mSettings.values(QString("data")); - Files::PathContainer dataDirs; + QList paths = mSettings.values(QString("data")); - for (const QString& path : paths) - { - dataDirs.emplace_back(Files::pathFromQString(path)); - } - - // Parse the data dirs to convert the tokenized paths - mCfgMgr.processPaths(dataDirs, /*basePath=*/""); mDataDirs.clear(); - for (const auto& dataDir : dataDirs) + for (const auto& dataDir : paths) { - if (is_directory(dataDir)) - mDataDirs.append(Files::pathToQString(dataDir)); + if (QDir(dataDir.value).exists()) + mDataDirs.append(dataDir); } // Do the same for data-local - QString local = mSettings.value(QString("data-local")); - - if (local.isEmpty()) - return; - - dataDirs.clear(); - dataDirs.emplace_back(Files::pathFromQString(local)); - - mCfgMgr.processPaths(dataDirs, /*basePath=*/""); + const QString& local = mSettings.value(QString("data-local")).value; - if (!dataDirs.empty()) - { - const auto& path = dataDirs.front(); - if (is_directory(path)) - mDataLocal = Files::pathToQString(path); - } + if (!local.isEmpty() && QDir(local).exists()) + mDataLocal = local; } QString Config::GameSettings::getResourcesVfs() const { - QString resources = mSettings.value(QString("resources"), QString("./resources")); + QString resources = mSettings.value(QString("resources"), { "./resources", "", "" }).value; resources += "/vfs"; return QFileInfo(resources).canonicalFilePath(); } -QStringList Config::GameSettings::values(const QString& key, const QStringList& defaultValues) const +QList Config::GameSettings::values( + const QString& key, const QList& defaultValues) const { if (!mSettings.values(key).isEmpty()) return mSettings.values(key); return defaultValues; } -bool Config::GameSettings::readFile(QTextStream& stream, bool ignoreContent) +bool Config::GameSettings::containsValue(const QString& key, const QString& value) const +{ + auto [itr, end] = mSettings.equal_range(key); + while (itr != end) + { + if (itr->value == value) + return true; + ++itr; + } + return false; +} + +bool Config::GameSettings::readFile(QTextStream& stream, const QString& context, bool ignoreContent) { - return readFile(stream, mSettings, ignoreContent); + if (readFile(stream, mSettings, context, ignoreContent)) + { + mContexts.push_back(context); + return true; + } + return false; } -bool Config::GameSettings::readUserFile(QTextStream& stream, bool ignoreContent) +bool Config::GameSettings::readUserFile(QTextStream& stream, const QString& context, bool ignoreContent) { - return readFile(stream, mUserSettings, ignoreContent); + return readFile(stream, mUserSettings, context, ignoreContent); } -bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap& settings, bool ignoreContent) +bool Config::GameSettings::readFile( + QTextStream& stream, QMultiMap& settings, const QString& context, bool ignoreContent) { - QMultiMap cache; + QMultiMap cache; QRegularExpression replaceRe("^\\s*replace\\s*=\\s*(.+)$"); QRegularExpression keyRe("^([^=]+)\\s*=\\s*(.+)$"); @@ -129,7 +129,7 @@ bool Config::GameSettings::readFile(QTextStream& stream, QMultiMap values = cache.values(key); values.append(settings.values(key)); - if (!values.contains(value)) + bool exists = false; + for (const auto& existingValue : values) + { + if (existingValue.value == value.value) + { + exists = true; + break; + } + } + if (!exists) { cache.insert(key, value); } @@ -216,7 +230,7 @@ bool Config::GameSettings::writeFile(QTextStream& stream) // Equivalent to stream << std::quoted(i.value(), '"', '&'), which won't work on QStrings. QChar delim = '\"'; QChar escape = '&'; - QString string = i.value(); + QString string = i.value().originalRepresentation; stream << delim; for (auto& it : string) @@ -231,7 +245,7 @@ bool Config::GameSettings::writeFile(QTextStream& stream) continue; } - stream << i.key() << "=" << i.value() << "\n"; + stream << i.key() << "=" << i.value().originalRepresentation << "\n"; } return true; @@ -386,10 +400,11 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) *iter = QString(); // assume no match QString key = match.captured(1); QString keyVal = match.captured(1) + "=" + match.captured(2); - QMultiMap::const_iterator i = mUserSettings.find(key); + QMultiMap::const_iterator i = mUserSettings.find(key); while (i != mUserSettings.end() && i.key() == key) { - QString settingLine = i.key() + "=" + i.value(); + // todo: does this need to handle paths? + QString settingLine = i.key() + "=" + i.value().originalRepresentation; QRegularExpressionMatch keyMatch = settingRegex.match(settingLine); if (keyMatch.hasMatch()) { @@ -441,7 +456,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) // Equivalent to settingLine += std::quoted(it.value(), '"', '&'), which won't work on QStrings. QChar delim = '\"'; QChar escape = '&'; - QString string = it.value(); + QString string = it.value().originalRepresentation; settingLine += delim; for (auto& iter : string) @@ -453,7 +468,7 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) settingLine += delim; } else - settingLine = it.key() + "=" + it.value(); + settingLine = it.key() + "=" + it.value().originalRepresentation; QRegularExpressionMatch match = settingRegex.match(settingLine); if (match.hasMatch()) @@ -512,11 +527,11 @@ bool Config::GameSettings::writeFileWithComments(QFile& file) bool Config::GameSettings::hasMaster() { bool result = false; - QStringList content = mSettings.values(QString(Config::GameSettings::sContentKey)); + QList content = mSettings.values(QString(Config::GameSettings::sContentKey)); for (int i = 0; i < content.count(); ++i) { - if (content.at(i).endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive) - || content.at(i).endsWith(QLatin1String(".esm"), Qt::CaseInsensitive)) + if (content.at(i).value.endsWith(QLatin1String(".omwgame"), Qt::CaseInsensitive) + || content.at(i).value.endsWith(QLatin1String(".esm"), Qt::CaseInsensitive)) { result = true; break; @@ -527,39 +542,49 @@ bool Config::GameSettings::hasMaster() } void Config::GameSettings::setContentList( - const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames) + const QList& dirNames, const QList& archiveNames, const QStringList& fileNames) { auto const reset = [this](const char* key, const QStringList& list) { remove(key); for (auto const& item : list) - setMultiValue(key, item); + setMultiValue(key, { item }); }; - reset(sDirectoryKey, dirNames); - reset(sArchiveKey, archiveNames); + remove(sDirectoryKey); + for (auto const& item : dirNames) + setMultiValue(sDirectoryKey, item); + remove(sArchiveKey); + for (auto const& item : archiveNames) + setMultiValue(sArchiveKey, item); reset(sContentKey, fileNames); } -QStringList Config::GameSettings::getDataDirs() const +QList Config::GameSettings::getDataDirs() const { return reverse(mDataDirs); } -QStringList Config::GameSettings::getArchiveList() const +QList Config::GameSettings::getArchiveList() const { // QMap returns multiple rows in LIFO order, so need to reverse return reverse(values(sArchiveKey)); } -QStringList Config::GameSettings::getContentList() const +QList Config::GameSettings::getContentList() const { // QMap returns multiple rows in LIFO order, so need to reverse return reverse(values(sContentKey)); } +bool Config::GameSettings::isUserSetting(const SettingValue& settingValue) const +{ + return settingValue.context.isEmpty() || settingValue.context == getUserContext(); +} + void Config::GameSettings::clear() { mSettings.clear(); + mContexts.clear(); mUserSettings.clear(); mDataDirs.clear(); mDataLocal.clear(); diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index bef108e2c7..14a8fcb155 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -17,33 +17,48 @@ namespace Files namespace Config { + struct SettingValue + { + QString value = ""; + // value as found in openmw.cfg, e.g. relative path with ?slug? + QString originalRepresentation = value; + // path of openmw.cfg, e.g. to resolve relative paths + QString context = ""; + + friend auto operator<=>(const SettingValue&, const SettingValue&) = default; + }; + class GameSettings { public: explicit GameSettings(const Files::ConfigurationManager& cfg); - inline QString value(const QString& key, const QString& defaultValue = QString()) + inline SettingValue value(const QString& key, const SettingValue& defaultValue = {}) { - return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); + return mSettings.contains(key) ? mSettings.value(key) : defaultValue; } - inline void setValue(const QString& key, const QString& value) + inline void setValue(const QString& key, const SettingValue& value) { mSettings.remove(key); mSettings.insert(key, value); mUserSettings.remove(key); - mUserSettings.insert(key, value); + if (isUserSetting(value)) + mUserSettings.insert(key, value); } - inline void setMultiValue(const QString& key, const QString& value) + inline void setMultiValue(const QString& key, const SettingValue& value) { - QStringList values = mSettings.values(key); + QList values = mSettings.values(key); if (!values.contains(value)) mSettings.insert(key, value); - values = mUserSettings.values(key); - if (!values.contains(value)) - mUserSettings.insert(key, value); + if (isUserSetting(value)) + { + values = mUserSettings.values(key); + if (!values.contains(value)) + mUserSettings.insert(key, value); + } } inline void remove(const QString& key) @@ -52,36 +67,48 @@ namespace Config mUserSettings.remove(key); } - QStringList getDataDirs() const; + QList getDataDirs() const; QString getResourcesVfs() const; - inline void removeDataDir(const QString& dir) + inline void removeDataDir(const QString& existingDir) { - if (!dir.isEmpty()) - mDataDirs.removeAll(dir); + if (!existingDir.isEmpty()) + { + // non-user settings can't be removed as we can't edit the openmw.cfg they're in + std::remove_if(mDataDirs.begin(), mDataDirs.end(), + [&](const SettingValue& dir) { return isUserSetting(dir) && dir.value == existingDir; }); + } } - inline void addDataDir(const QString& dir) + + inline void addDataDir(const SettingValue& dir) { - if (!dir.isEmpty()) + if (!dir.value.isEmpty()) mDataDirs.append(dir); } + inline QString getDataLocal() const { return mDataLocal; } bool hasMaster(); - QStringList values(const QString& key, const QStringList& defaultValues = QStringList()) const; + QList values(const QString& key, const QList& defaultValues = {}) const; + bool containsValue(const QString& key, const QString& value) const; - bool readFile(QTextStream& stream, bool ignoreContent = false); - bool readFile(QTextStream& stream, QMultiMap& settings, bool ignoreContent = false); - bool readUserFile(QTextStream& stream, bool ignoreContent = false); + bool readFile(QTextStream& stream, const QString& context, bool ignoreContent = false); + bool readFile(QTextStream& stream, QMultiMap& settings, const QString& context, + bool ignoreContent = false); + bool readUserFile(QTextStream& stream, const QString& context, bool ignoreContent = false); bool writeFile(QTextStream& stream); bool writeFileWithComments(QFile& file); - QStringList getArchiveList() const; - void setContentList(const QStringList& dirNames, const QStringList& archiveNames, const QStringList& fileNames); - QStringList getContentList() const; + QList getArchiveList() const; + void setContentList( + const QList& dirNames, const QList& archiveNames, const QStringList& fileNames); + QList getContentList() const; + + const QString& getUserContext() const { return mContexts.back(); } + bool isUserSetting(const SettingValue& settingValue) const; void clear(); @@ -89,10 +116,12 @@ namespace Config const Files::ConfigurationManager& mCfgMgr; void validatePaths(); - QMultiMap mSettings; - QMultiMap mUserSettings; + QMultiMap mSettings; + QMultiMap mUserSettings; - QStringList mDataDirs; + QStringList mContexts; + + QList mDataDirs; QString mDataLocal; static const char sArchiveKey[]; @@ -102,4 +131,7 @@ namespace Config static bool isOrderedLine(const QString& line); }; } + +Q_DECLARE_METATYPE(Config::SettingValue) + #endif // GAMESETTINGS_HPP diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index 2f4decb762..f9f067e58a 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -223,9 +223,25 @@ QStringList Config::LauncherSettings::getContentLists() void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) { // obtain content list from game settings (if present) - QStringList dirs(gameSettings.getDataDirs()); - const QStringList archives(gameSettings.getArchiveList()); - const QStringList files(gameSettings.getContentList()); + QList dirs(gameSettings.getDataDirs()); + dirs.erase(std::remove_if( + dirs.begin(), dirs.end(), [&](const SettingValue& dir) { return !gameSettings.isUserSetting(dir); }), + dirs.end()); + // archives and content files aren't preprocessed, so we don't need to track their original form + const QList archivesOriginal(gameSettings.getArchiveList()); + QStringList archives; + for (const auto& archive : archivesOriginal) + { + if (gameSettings.isUserSetting(archive)) + archives.push_back(archive.value); + } + const QList filesOriginal(gameSettings.getContentList()); + QStringList files; + for (const auto& file : filesOriginal) + { + if (gameSettings.isUserSetting(file)) + files.push_back(file.value); + } // if openmw.cfg has no content, exit so we don't create an empty content list. if (dirs.isEmpty() || files.isEmpty()) @@ -236,14 +252,25 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) // local data directory and resources/vfs are not part of any profile const auto resourcesVfs = gameSettings.getResourcesVfs(); const auto dataLocal = gameSettings.getDataLocal(); - dirs.removeAll(resourcesVfs); - dirs.removeAll(dataLocal); + dirs.erase( + std::remove_if(dirs.begin(), dirs.end(), [&](const SettingValue& dir) { return dir.value == resourcesVfs; }), + dirs.end()); + dirs.erase( + std::remove_if(dirs.begin(), dirs.end(), [&](const SettingValue& dir) { return dir.value == dataLocal; }), + dirs.end()); // if any existing profile in launcher matches the content list, make that profile the default for (const QString& listName : getContentLists()) { - if (files == getContentListFiles(listName) && archives == getArchiveList(listName) - && dirs == getDataDirectoryList(listName)) + const auto& listDirs = getDataDirectoryList(listName); + if (dirs.length() != listDirs.length()) + continue; + for (int i = 0; i < dirs.length(); ++i) + { + if (dirs[i].value != listDirs[i]) + continue; + } + if (files == getContentListFiles(listName) && archives == getArchiveList(listName)) { setCurrentContentListName(listName); return; @@ -253,7 +280,10 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) // otherwise, add content list QString newContentListName(makeNewContentListName()); setCurrentContentListName(newContentListName); - setContentList(newContentListName, dirs, archives, files); + QStringList newListDirs; + for (const auto& dir : dirs) + newListDirs.push_back(dir.value); + setContentList(newContentListName, newListDirs, archives, files); } void Config::LauncherSettings::setContentList(const QString& contentListName, const QStringList& dirNames, diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 003f2ee241..66fde2063f 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -220,7 +220,8 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int if (file == mGameFile) return QVariant(); - return (file->builtIn() || file->fromAnotherConfigFile() || mCheckedFiles.contains(file)) ? Qt::Checked : Qt::Unchecked; + return (file->builtIn() || file->fromAnotherConfigFile() || mCheckedFiles.contains(file)) ? Qt::Checked + : Qt::Unchecked; } case Qt::UserRole: @@ -467,6 +468,8 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0) file->setBuiltIn(true); + file->setFromAnotherConfigFile(mNonUserContent.contains(info.fileName().toLower())); + if (info.fileName().endsWith(".omwscripts", Qt::CaseInsensitive)) { file->setDate(info.lastModified()); @@ -660,6 +663,28 @@ void ContentSelectorModel::ContentModel::setNew(const QString& filepath, bool is mNewFiles[filepath] = isNew; } +void ContentSelectorModel::ContentModel::setNonUserContent(const QStringList& fileList) +{ + mNonUserContent.clear(); + for (const auto& file : fileList) + mNonUserContent.insert(file.toLower()); + for (auto* file : mFiles) + file->setFromAnotherConfigFile(mNonUserContent.contains(file->fileName().toLower())); + + int insertPosition = 0; + while (mFiles.at(insertPosition)->builtIn()) + ++insertPosition; + + for (const auto& filepath : fileList) + { + const EsmFile* file = item(filepath); + int filePosition = indexFromItem(file).row(); + mFiles.move(filePosition, insertPosition++); + } + + sortFiles(); +} + bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile* file) const { return mPluginsWithLoadOrderError.contains(file->filePath()); diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index f754b9ea30..3cc05fd3cb 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -62,6 +62,7 @@ namespace ContentSelectorModel bool setCheckState(const QString& filepath, bool isChecked); bool isNew(const QString& filepath) const; void setNew(const QString& filepath, bool isChecked); + void setNonUserContent(const QStringList& fileList); void setContentList(const QStringList& fileList); ContentFileList checkedItems() const; void uncheckAll(); @@ -85,7 +86,7 @@ namespace ContentSelectorModel const EsmFile* mGameFile; ContentFileList mFiles; - QStringList mArchives; + QSet mNonUserContent; std::set mCheckedFiles; QHash mNewFiles; QSet mPluginsWithLoadOrderError; diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 00c32e272d..a3fd224390 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -123,6 +123,11 @@ void ContentSelectorView::ContentSelector::buildContextMenu() mContextMenu->addAction(tr("&Copy Path(s) to Clipboard"), this, SLOT(slotCopySelectedItemsPaths())); } +void ContentSelectorView::ContentSelector::setNonUserContent(const QStringList& fileList) +{ + mContentModel->setNonUserContent(fileList); +} + void ContentSelectorView::ContentSelector::setProfileContent(const QStringList& fileList) { clearCheckStates(); @@ -336,4 +341,4 @@ void ContentSelectorView::ContentSelector::slotSearchFilterTextChanged(const QSt void ContentSelectorView::ContentSelector::slotRowsMoved() { ui->addonView->selectionModel()->clearSelection(); -} \ No newline at end of file +} diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 2b739645ba..2fdd38c799 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -40,6 +40,7 @@ namespace ContentSelectorView void sortFiles(); bool containsDataFiles(const QString& path); void clearFiles(); + void setNonUserContent(const QStringList& fileList); void setProfileContent(const QStringList& fileList); void clearCheckStates(); From 1ae2cc82a1866d5178be218397bccefc0cc25793 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 6 Mar 2024 00:46:01 +0000 Subject: [PATCH 192/451] I do not know how this escaped formatting locally. --- components/contentselector/model/esmfile.hpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index 42b6cfeff6..4703df562c 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -62,16 +62,18 @@ namespace ContentSelectorModel QString toolTip() const { QString tooltip = mTooltipTemlate.arg(mAuthor) - .arg(mVersion) - .arg(mModified.toString(Qt::ISODate)) - .arg(mPath) - .arg(mDescription) - .arg(mGameFiles.join(", ")); + .arg(mVersion) + .arg(mModified.toString(Qt::ISODate)) + .arg(mPath) + .arg(mDescription) + .arg(mGameFiles.join(", ")); if (mBuiltIn) tooltip += tr("
This content file cannot be disabled because it is part of OpenMW.
"); else if (mFromAnotherConfigFile) - tooltip += tr("
This content file cannot be disabled because it is enabled in a config file other than the user one.
"); + tooltip += tr( + "
This content file cannot be disabled because it is enabled in a config file other than " + "the user one.
"); return tooltip; } From c23e5e105997209043acc8322c7cf055f40ebeb3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 6 Mar 2024 00:47:31 +0000 Subject: [PATCH 193/451] I guess these aren't wired up as a dependency of the build I know the qm generation is so maybe it needs another look --- files/lang/components_de.ts | 8 ++++++++ files/lang/components_fr.ts | 8 ++++++++ files/lang/components_ru.ts | 8 ++++++++ files/lang/launcher_de.ts | 20 ++++++++++++++++++++ files/lang/launcher_fr.ts | 20 ++++++++++++++++++++ files/lang/launcher_ru.ts | 20 ++++++++++++++++++++ files/lang/wizard_de.ts | 28 ++++++++++++++-------------- files/lang/wizard_fr.ts | 28 ++++++++++++++-------------- files/lang/wizard_ru.ts | 28 ++++++++++++++-------------- 9 files changed, 126 insertions(+), 42 deletions(-) diff --git a/files/lang/components_de.ts b/files/lang/components_de.ts index 59ba558328..76b90229fc 100644 --- a/files/lang/components_de.ts +++ b/files/lang/components_de.ts @@ -29,6 +29,14 @@ <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> + + <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> + + + + <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> + + ContentSelectorView::ContentSelector diff --git a/files/lang/components_fr.ts b/files/lang/components_fr.ts index c1c70ba277..309424cda4 100644 --- a/files/lang/components_fr.ts +++ b/files/lang/components_fr.ts @@ -29,6 +29,14 @@ <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> + + <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> + + + + <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> + + ContentSelectorView::ContentSelector diff --git a/files/lang/components_ru.ts b/files/lang/components_ru.ts index cca6591afe..1618961914 100644 --- a/files/lang/components_ru.ts +++ b/files/lang/components_ru.ts @@ -29,6 +29,14 @@ <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> <b>Автор:</b> %1<br/><b>Версия формата данных:</b> %2<br/><b>Дата изменения:</b> %3<br/><b>Путь к файлу:</b><br/>%4<br/><br/><b>Описание:</b><br/>%5<br/><br/><b>Зависимости: </b>%6<br/> + + <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> + + + + <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> + + ContentSelectorView::ContentSelector diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 925733f9d3..6145d21d28 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -370,6 +370,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov &Uncheck Selected + + Resolved as %1 + + + + This is the data-local directory and cannot be disabled + + + + This directory is part of OpenMW and cannot be disabled + + + + This directory is enabled in an openmw.cfg other than the user one + + + + This archive is enabled in an openmw.cfg other than the user one + + Launcher::GraphicsPage diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 3471fc6c5c..3affe3c83f 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -370,6 +370,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov &Uncheck Selected + + Resolved as %1 + + + + This is the data-local directory and cannot be disabled + + + + This directory is part of OpenMW and cannot be disabled + + + + This directory is enabled in an openmw.cfg other than the user one + + + + This archive is enabled in an openmw.cfg other than the user one + + Launcher::GraphicsPage diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index ec7aeccc57..4a71267eb4 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -372,6 +372,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov &Uncheck Selected &Отключить выбранные + + Resolved as %1 + + + + This is the data-local directory and cannot be disabled + + + + This directory is part of OpenMW and cannot be disabled + + + + This directory is enabled in an openmw.cfg other than the user one + + + + This archive is enabled in an openmw.cfg other than the user one + + Launcher::GraphicsPage diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts index 7bf54e90b1..4fecd1de72 100644 --- a/files/lang/wizard_de.ts +++ b/files/lang/wizard_de.ts @@ -590,59 +590,59 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - + OpenMW Wizard - - + + Error opening Wizard log file - - - + + + <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + Error opening OpenMW configuration file - + Quit Wizard - + Are you sure you want to exit the Wizard? - + Error creating OpenMW configuration directory - + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - + + Error writing OpenMW configuration file diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index 7f42087dbf..c2950c0a42 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -590,59 +590,59 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - + OpenMW Wizard - - + + Error opening Wizard log file - - - + + + <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + Error opening OpenMW configuration file - + Quit Wizard - + Are you sure you want to exit the Wizard? - + Error creating OpenMW configuration directory - + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - + + Error writing OpenMW configuration file diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts index 3113774cd3..2461204681 100644 --- a/files/lang/wizard_ru.ts +++ b/files/lang/wizard_ru.ts @@ -592,59 +592,59 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - + OpenMW Wizard Мастер OpenMW - - + + Error opening Wizard log file Не удалось открыть лог-файл Мастера - - - + + + <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось открыть %1 для записи</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - + <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось открыть %1 для чтения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - + Error opening OpenMW configuration file Не удалось открыть файл с настройками OpenMW - + Quit Wizard Завершить работу Мастера - + Are you sure you want to exit the Wizard? Вы уверены, что хотите завершить работу Мастера? - + Error creating OpenMW configuration directory Не удалось создать директорию для настроек OpenMW - + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось создать %1</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - + + Error writing OpenMW configuration file Не удалось записать данные в файл с настройками OpenMW From bf24bb71b1a5e4f0259e8c3cd94227f331f4affc Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 6 Mar 2024 01:23:51 +0000 Subject: [PATCH 194/451] Explicitly use std::strong_ordering Otherwise it's ambiguous how to build <=> from <, == and > --- components/config/gamesettings.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index 14a8fcb155..96e0864a9e 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -25,7 +25,7 @@ namespace Config // path of openmw.cfg, e.g. to resolve relative paths QString context = ""; - friend auto operator<=>(const SettingValue&, const SettingValue&) = default; + friend std::strong_ordering operator<=>(const SettingValue&, const SettingValue&) = default; }; class GameSettings From 7f1a6a81874a4b4272d5feefbc7316e23504f4ee Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 6 Mar 2024 01:37:40 +0000 Subject: [PATCH 195/451] Fix file that's not used on Windows --- apps/wizard/installationpage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 60e9f3ccf9..aec64e275d 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -18,7 +18,7 @@ Wizard::InstallationPage::InstallationPage(QWidget* parent, Config::GameSettings mFinished = false; mThread = std::make_unique(); - mUnshield = std::make_unique(mGameSettings.value("morrowind-bsa-filesize").toLongLong()); + mUnshield = std::make_unique(mGameSettings.value("morrowind-bsa-filesize").value.toLongLong()); mUnshield->moveToThread(mThread.get()); connect(mThread.get(), &QThread::started, mUnshield.get(), &UnshieldWorker::extract); From 1499dd2654fa025448a715a41f32d2d21c566105 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 6 Mar 2024 18:16:55 +0100 Subject: [PATCH 196/451] Add getCompositeSize and handle NPC data --- components/esm/decompose.hpp | 7 +++ components/esm3/loadnpc.cpp | 93 ++++++++++++++++-------------------- components/esm3/loadnpc.hpp | 2 - components/esm3/loadpgrd.cpp | 2 +- 4 files changed, 50 insertions(+), 54 deletions(-) diff --git a/components/esm/decompose.hpp b/components/esm/decompose.hpp index eb6f5070d4..f9fecec067 100644 --- a/components/esm/decompose.hpp +++ b/components/esm/decompose.hpp @@ -5,6 +5,13 @@ namespace ESM { template void decompose(T&& value, const auto& apply) = delete; + + std::size_t getCompositeSize(const auto& value) + { + std::size_t result = 0; + decompose(value, [&](const auto&... args) { result = (0 + ... + sizeof(args)); }); + return result; + } } #endif diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 58a8bfa55e..03c47d4d73 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -3,8 +3,34 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { + namespace + { + struct NPDTstruct12 + { + NPC::NPDTstruct52& mStruct; + }; + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding1 = 0; + char padding2 = 0; + f(v.mLevel, v.mAttributes, v.mSkills, padding1, v.mHealth, v.mMana, v.mFatigue, v.mDisposition, v.mReputation, + v.mRank, padding2, v.mGold); + } + + template T> + void decompose(T&& v, const auto& f) + { + char padding[] = { 0, 0, 0 }; + f(v.mStruct.mLevel, v.mStruct.mDisposition, v.mStruct.mReputation, v.mStruct.mRank, padding, v.mStruct.mGold); + } + void NPC::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -56,37 +82,25 @@ namespace ESM case fourCC("NPDT"): hasNpdt = true; esm.getSubHeader(); - if (esm.getSubSize() == 52) + if (esm.getSubSize() == getCompositeSize(mNpdt)) { mNpdtType = NPC_DEFAULT; - esm.getT(mNpdt.mLevel); - esm.getT(mNpdt.mAttributes); - esm.getT(mNpdt.mSkills); - esm.getT(mNpdt.mUnknown1); - esm.getT(mNpdt.mHealth); - esm.getT(mNpdt.mMana); - esm.getT(mNpdt.mFatigue); - esm.getT(mNpdt.mDisposition); - esm.getT(mNpdt.mReputation); - esm.getT(mNpdt.mRank); - esm.getT(mNpdt.mUnknown2); - esm.getT(mNpdt.mGold); + esm.getComposite(mNpdt); } - else if (esm.getSubSize() == 12) + else { - mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; - - // Clearing the mNdpt struct to initialize all values - blankNpdt(); - esm.getT(mNpdt.mLevel); - esm.getT(mNpdt.mDisposition); - esm.getT(mNpdt.mReputation); - esm.getT(mNpdt.mRank); - esm.skip(3); - esm.getT(mNpdt.mGold); + NPDTstruct12 data{ mNpdt }; + if (esm.getSubSize() == getCompositeSize(data)) + { + mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; + + // Clearing the mNdpt struct to initialize all values + blankNpdt(); + esm.getComposite(data); + } + else + esm.fail("NPC_NPDT must be 12 or 52 bytes long"); } - else - esm.fail("NPC_NPDT must be 12 or 52 bytes long"); break; case fourCC("FLAG"): hasFlags = true; @@ -154,32 +168,11 @@ namespace ESM if (mNpdtType == NPC_DEFAULT) { - esm.startSubRecord("NPDT"); - esm.writeT(mNpdt.mLevel); - esm.writeT(mNpdt.mAttributes); - esm.writeT(mNpdt.mSkills); - esm.writeT(mNpdt.mUnknown1); - esm.writeT(mNpdt.mHealth); - esm.writeT(mNpdt.mMana); - esm.writeT(mNpdt.mFatigue); - esm.writeT(mNpdt.mDisposition); - esm.writeT(mNpdt.mReputation); - esm.writeT(mNpdt.mRank); - esm.writeT(mNpdt.mUnknown2); - esm.writeT(mNpdt.mGold); - esm.endRecord("NPDT"); + esm.writeNamedComposite("NPDT", mNpdt); } else if (mNpdtType == NPC_WITH_AUTOCALCULATED_STATS) { - esm.startSubRecord("NPDT"); - esm.writeT(mNpdt.mLevel); - esm.writeT(mNpdt.mDisposition); - esm.writeT(mNpdt.mReputation); - esm.writeT(mNpdt.mRank); - constexpr char padding[] = { 0, 0, 0 }; - esm.writeT(padding); - esm.writeT(mNpdt.mGold); - esm.endRecord("NPDT"); + esm.writeNamedComposite("NPDT", NPDTstruct12{ const_cast(mNpdt) }); } esm.writeHNT("FLAG", ((mBloodType << 10) + mFlags)); @@ -238,9 +231,7 @@ namespace ESM mNpdt.mReputation = 0; mNpdt.mHealth = mNpdt.mMana = mNpdt.mFatigue = 0; mNpdt.mDisposition = 0; - mNpdt.mUnknown1 = 0; mNpdt.mRank = 0; - mNpdt.mUnknown2 = 0; mNpdt.mGold = 0; } diff --git a/components/esm3/loadnpc.hpp b/components/esm3/loadnpc.hpp index 76930365c8..40ec0f0347 100644 --- a/components/esm3/loadnpc.hpp +++ b/components/esm3/loadnpc.hpp @@ -83,10 +83,8 @@ namespace ESM // mSkill can grow up to 200, it must be unsigned std::array mSkills; - char mUnknown1; uint16_t mHealth, mMana, mFatigue; unsigned char mDisposition, mReputation, mRank; - char mUnknown2; int32_t mGold; }; // 52 bytes diff --git a/components/esm3/loadpgrd.cpp b/components/esm3/loadpgrd.cpp index ebd51dcff0..c438fd73eb 100644 --- a/components/esm3/loadpgrd.cpp +++ b/components/esm3/loadpgrd.cpp @@ -73,7 +73,7 @@ namespace ESM { esm.getSubHeader(); uint32_t size = esm.getSubSize(); - if (size != 16u * mData.mPoints) + if (size != getCompositeSize(Point{}) * mData.mPoints) esm.fail("Path point subrecord size mismatch"); else { From 312f6c90e018bc8a4a6e18576b1b6a8b1ce0b581 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 6 Mar 2024 18:13:21 +0100 Subject: [PATCH 197/451] Rewrite SkillProgression.skillUsed to allow directly adding xp instead of going via useType. --- files/data/builtin.omwscripts | 2 +- .../omw/mechanics/playercontroller.lua | 19 ++-- files/data/scripts/omw/skillhandlers.lua | 97 +++++++++++-------- 3 files changed, 67 insertions(+), 51 deletions(-) diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index 4021ef9f11..81fb76f023 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -12,6 +12,7 @@ GLOBAL: scripts/omw/cellhandlers.lua GLOBAL: scripts/omw/usehandlers.lua GLOBAL: scripts/omw/worldeventhandlers.lua CREATURE, NPC, PLAYER: scripts/omw/mechanics/animationcontroller.lua +PLAYER: scripts/omw/skillhandlers.lua PLAYER: scripts/omw/mechanics/playercontroller.lua MENU: scripts/omw/camera/settings.lua MENU: scripts/omw/input/settings.lua @@ -19,7 +20,6 @@ PLAYER: scripts/omw/input/playercontrols.lua PLAYER: scripts/omw/camera/camera.lua PLAYER: scripts/omw/input/actionbindings.lua PLAYER: scripts/omw/input/smoothmovement.lua -PLAYER: scripts/omw/skillhandlers.lua NPC,CREATURE: scripts/omw/ai.lua # User interface diff --git a/files/data/scripts/omw/mechanics/playercontroller.lua b/files/data/scripts/omw/mechanics/playercontroller.lua index 935bf5029f..333e097404 100644 --- a/files/data/scripts/omw/mechanics/playercontroller.lua +++ b/files/data/scripts/omw/mechanics/playercontroller.lua @@ -83,18 +83,16 @@ local function skillLevelUpHandler(skillid, source, params) if not source or source == I.SkillProgression.SKILL_INCREASE_SOURCES.Usage then skillStat.progress = 0 end end -local function skillUsedHandler(skillid, useType, params) +local function skillUsedHandler(skillid, params) if NPC.isWerewolf(self) then return false end - if params.skillGain then - local skillStat = NPC.stats.skills[skillid](self) - skillStat.progress = skillStat.progress + params.skillGain + local skillStat = NPC.stats.skills[skillid](self) + skillStat.progress = skillStat.progress + params.skillGain / I.SkillProgression.getSkillProgressRequirement(skillid) - if skillStat.progress >= 1 then - I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Usage) - end + if skillStat.progress >= 1 then + I.SkillProgression.skillLevelUp(skillid, I.SkillProgression.SKILL_INCREASE_SOURCES.Usage) end end @@ -106,14 +104,11 @@ local function onUpdate() processAutomaticDoors() end -local function onActive() - I.SkillProgression.addSkillUsedHandler(skillUsedHandler) - I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler) -end +I.SkillProgression.addSkillUsedHandler(skillUsedHandler) +I.SkillProgression.addSkillLevelUpHandler(skillLevelUpHandler) return { engineHandlers = { onUpdate = onUpdate, - onActive = onActive, }, } diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index 57fc224cee..db726e8474 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -7,7 +7,7 @@ local Skill = core.stats.Skill --- -- Table of skill use types defined by morrowind. --- Each entry corresponds to an index into the available skill gain values +-- Each entry corresponds to an index into the available skill gain values -- of a @{openmw.types#SkillRecord} -- @type SkillUseType -- @field #number Armor_HitByOpponent 0 @@ -35,7 +35,7 @@ local Skill = core.stats.Skill -- @field #number Athletics_SwimOneSecond 1 --- --- Table of valid sources for skill increases +-- Table of all existing sources for skill increases. Any sources not listed below will be treated as equal to Trainer. -- @type SkillLevelUpSource -- @field #string Book book -- @field #string Trainer trainer @@ -52,10 +52,16 @@ local function tableHasValue(table, value) return false end -local function getSkillProgressRequirementUnorm(npc, skillid) - local npcRecord = NPC.record(npc) +local function shallowCopy(t1) + local t2 = {} + for key, value in pairs(t1) do t2[key] = value end + return t2 +end + +local function getSkillProgressRequirement(skillid) + local npcRecord = NPC.record(self) local class = NPC.classes.record(npcRecord.class) - local skillStat = NPC.stats.skills[skillid](npc) + local skillStat = NPC.stats.skills[skillid](self) local skillRecord = Skill.record(skillid) local factor = core.getGMST('fMiscSkillBonus') @@ -72,32 +78,33 @@ local function getSkillProgressRequirementUnorm(npc, skillid) return (skillStat.base + 1) * factor end -local function skillUsed(skillid, useType, scale) + +local function skillUsed(skillid, options) if #skillUsedHandlers == 0 then -- If there are no handlers, then there won't be any effect, so skip calculations return end + + -- Make a copy so we don't change the caller's table + options = shallowCopy(options) + + -- Compute use value if it was not supplied directly + if not options.skillGain then + if not options.useType or options.useType > 3 or options.useType < 0 then + print('Error: Unknown useType: '..tostring(options.useType)) + return + end + local skillStat = NPC.stats.skills[skillid](self) + local skillRecord = Skill.record(skillid) + options.skillGain = skillRecord.skillGain[options.useType + 1] - if useType > 3 or useType < 0 then - print('Error: Unknown useType: '..tostring(useType)) - return + if options.scale then + options.skillGain = options.skillGain * options.scale + end end - -- Compute skill gain - local skillStat = NPC.stats.skills[skillid](self) - local skillRecord = Skill.record(skillid) - local skillGainUnorm = skillRecord.skillGain[useType + 1] - if scale then skillGainUnorm = skillGainUnorm * scale end - local skillProgressRequirementUnorm = getSkillProgressRequirementUnorm(self, skillid) - local skillGain = skillGainUnorm / skillProgressRequirementUnorm - - -- Put skill gain in a table so that handlers can modify it - local options = { - skillGain = skillGain, - } - for i = #skillUsedHandlers, 1, -1 do - if skillUsedHandlers[i](skillid, useType, options) == false then + if skillUsedHandlers[i](skillid, options) == false then return end end @@ -156,8 +163,8 @@ return { -- end) -- -- -- Scale sneak skill progression based on active invisibility effects - -- I.SkillProgression.addSkillUsedHandler(function(skillid, useType, params) - -- if skillid == 'sneak' and useType == I.SkillProgression.SKILL_USE_TYPES.Sneak_AvoidNotice then + -- I.SkillProgression.addSkillUsedHandler(function(skillid, params) + -- if skillid == 'sneak' and params.useType == I.SkillProgression.SKILL_USE_TYPES.Sneak_AvoidNotice then -- local activeEffects = Actor.activeEffects(self) -- local visibility = activeEffects:getEffect(core.magic.EFFECT_TYPE.Chameleon).magnitude / 100 -- visibility = visibility + activeEffects:getEffect(core.magic.EFFECT_TYPE.Invisibility).magnitude @@ -172,9 +179,10 @@ return { -- @field [parent=#SkillProgression] #number version version = 0, - --- Add new skill level up handler for this actor + --- Add new skill level up handler for this actor. + -- For load order consistency, handlers should be added in the body if your script. -- If `handler(skillid, source, options)` returns false, other handlers (including the default skill level up handler) - -- will be skipped. Where skillid and source are the parameters passed to @{SkillProgression#skillLevelUp}, and options is + -- will be skipped. Where skillid and source are the parameters passed to @{#skillLevelUp}, and options is -- a modifiable table of skill level up values, and can be modified to change the behavior of later handlers. -- These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values: -- @@ -191,14 +199,11 @@ return { skillLevelUpHandlers[#skillLevelUpHandlers + 1] = handler end, - --- Add new skillUsed handler for this actor - -- If `handler(skillid, useType, options)` returns false, other handlers (including the default skill progress handler) - -- will be skipped. Where skillid and useType are the parameters passed to @{SkillProgression#skillUsed}, - -- and options is a modifiable table of skill progression values, and can be modified to change the behavior of later handlers. - -- By default contains the single value: - -- - -- * `skillGain` - The numeric amount of skill progress gained, normalized to the range 0 to 1, where 1 is a full level. - -- + --- Add new skillUsed handler for this actor. + -- For load order consistency, handlers should be added in the body of your script. + -- If `handler(skillid, options)` returns false, other handlers (including the default skill progress handler) + -- will be skipped. Where options is a modifiable table of skill progression values, and can be modified to change the behavior of later handlers. + -- Contains a `skillGain` value as well as a shallow copy of the options passed to @{#skillUsed}. -- @function [parent=#SkillProgression] addSkillUsedHandler -- @param #function handler The handler. addSkillUsedHandler = function(handler) @@ -208,8 +213,19 @@ return { --- Trigger a skill use, activating relevant handlers -- @function [parent=#SkillProgression] skillUsed -- @param #string skillid The if of the skill that was used - -- @param #SkillUseType useType A number from 0 to 3 (inclusive) representing the way the skill was used, with each use type having a different skill progression rate. Available use types and its effect is skill specific. See @{SkillProgression#skillUseType} - -- @param #number scale A number that linearly scales the skill progress received from this use. Defaults to 1. + -- @param options A table of parameters. Must contain one of `skillGain` or `useType`. It's best to always include `useType` if applicable, even if you set `skillGain`, as it may be used + -- by handlers to make decisions. See the addSkillUsedHandler example at the top of this page. + -- + -- * `skillGain` - The numeric amount of skill to be gained. + -- * `useType` - #SkillUseType, A number from 0 to 3 (inclusive) representing the way the skill was used, with each use type having a different skill progression rate. Available use types and its effect is skill specific. See @{#SkillUseType} + -- + -- And may contain the following optional parameter: + -- + -- * `scale` - A numeric value used to scale the skill gain. Ignored if the `skillGain` parameter is set. + -- + -- Note that a copy of this table is passed to skill used handlers, so any parameters passed to this method will also be passed to the handlers. This can be used to provide additional information to + -- custom handlers when making custom skill progressions. + -- skillUsed = skillUsed, --- @{#SkillUseType} @@ -256,11 +272,16 @@ return { Usage = 'usage', Trainer = 'trainer', }, + + --- Compute the total skill gain required to level up a skill based on its current level, and other modifying factors such as major skills and specialization. + -- @function [parent=#SkillProgression] getSkillProgressRequirement + -- @param #string skillid The id of the skill to compute skill progress requirement for + getSkillProgressRequirement = getSkillProgressRequirement }, engineHandlers = { -- Use the interface in these handlers so any overrides will receive the calls. _onSkillUse = function (skillid, useType, scale) - I.SkillProgression.skillUsed(skillid, useType, scale) + I.SkillProgression.skillUsed(skillid, {useType = useType, scale = scale}) end, _onSkillLevelUp = function (skillid, source) I.SkillProgression.skillLevelUp(skillid, source) From 5acfb0785031f3631ca59c4a5daffe533bce926f Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 6 Mar 2024 20:51:48 +0100 Subject: [PATCH 198/451] Fix build with OSG_USE_UTF8_FILENAME --- components/misc/osgpluginchecker.cpp.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index b570c8f858..f519447752 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -41,7 +41,7 @@ namespace Misc for (const auto& path : filepath) { #ifdef OSG_USE_UTF8_FILENAME - std::filesystem::path osgPath{ stringToU8String(path) }; + std::filesystem::path osgPath{ StringUtils::stringToU8String(path) }; #else std::filesystem::path osgPath{ path }; #endif @@ -52,7 +52,7 @@ namespace Misc { osgPath = osgPath.parent_path(); #ifdef OSG_USE_UTF8_FILENAME - std::string extraPath = u8StringToString(osgPath.u8string_view()); + std::string extraPath = StringUtils::u8StringToString(osgPath.u8string()); #else std::string extraPath = osgPath.string(); #endif @@ -67,7 +67,7 @@ namespace Misc if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), [&](std::string_view availablePlugin) { #ifdef OSG_USE_UTF8_FILENAME - std::filesystem::path pluginPath{ stringToU8String(availablePlugin) }; + std::filesystem::path pluginPath{ StringUtils::stringToU8String(availablePlugin) }; #else std::filesystem::path pluginPath {availablePlugin}; #endif From 7a5493796fa098af5deeca07befc658ffe08db8e Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 1 Mar 2024 22:57:41 +0100 Subject: [PATCH 199/451] Update setting page elements when possible --- files/data/scripts/omw/settings/menu.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 704b29f032..2246b5ad7e 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -329,10 +329,14 @@ local function renderPage(page, options) bigSpacer, }, } - if options.element then options.element:destroy() end options.name = l10n(page.name) - options.element = ui.create(layout) options.searchHints = generateSearchHints(page) + if options.element then + options.element.layout = layout + options.element:update() + else + options.element = ui.create(layout) + end end local function onSettingChanged(global) @@ -461,9 +465,6 @@ local function registerPage(options) } pages[page.key] = page groups[page.key] = groups[page.key] or {} - if pageOptions[page.key] then - pageOptions[page.key].element:destroy() - end pageOptions[page.key] = pageOptions[page.key] or {} renderPage(page, pageOptions[page.key]) ui.registerSettingsPage(pageOptions[page.key]) From 9ae61f19322c711acec37e51af83b45e72b5990c Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 6 Mar 2024 22:01:26 +0100 Subject: [PATCH 200/451] Fix child UI Elements created in the same frame as parent --- apps/openmw/mwlua/uibindings.cpp | 13 +++++---- components/lua_ui/element.cpp | 46 ++++++++++++++++++-------------- components/lua_ui/element.hpp | 14 +++++++--- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index f21fdb337a..a8df03ba25 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -134,7 +134,10 @@ namespace MWLua }; api["updateAll"] = [luaManager = context.mLuaManager, menu]() { - LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { e->mUpdate = true; }); + LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { + if (e->mState == LuaUi::Element::Created) + e->mState = LuaUi::Element::Update; + }); luaManager->addAction([menu]() { LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { e->update(); }); }, "Update all menu UI elements"); }; @@ -305,15 +308,15 @@ namespace MWLua element["layout"] = sol::property([](const LuaUi::Element& element) { return element.mLayout; }, [](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; }); element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { - if (element->mDestroy || element->mUpdate) + if (element->mState != LuaUi::Element::Created) return; - element->mUpdate = true; + element->mState = LuaUi::Element::Update; luaManager->addAction([element] { wrapAction(element, [&] { element->update(); }); }, "Update UI"); }; element["destroy"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { - if (element->mDestroy) + if (element->mState == LuaUi::Element::Destroyed) return; - element->mDestroy = true; + element->mState = LuaUi::Element::Destroy; luaManager->addAction( [element] { wrapAction(element, [&] { LuaUi::Element::erase(element.get()); }); }, "Destroy UI"); }; diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 5a54cd91b5..6e7fe9ee16 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -89,12 +89,16 @@ namespace LuaUi root->updateCoord(); } - WidgetExtension* pluckElementRoot(const sol::object& child) + WidgetExtension* pluckElementRoot(const sol::object& child, uint64_t depth) { std::shared_ptr element = child.as>(); - WidgetExtension* root = element->mRoot; - if (!root) + if (element->mState == Element::Destroyed || element->mState == Element::Destroy) throw std::logic_error("Using a destroyed element as a layout child"); + // child Element was created in the same frame and its action hasn't been processed yet + if (element->mState == Element::New) + element->create(depth + 1); + WidgetExtension* root = element->mRoot; + assert(root); WidgetExtension* parent = root->getParent(); if (parent) { @@ -107,7 +111,7 @@ namespace LuaUi return root; } - WidgetExtension* createWidget(const sol::table& layout, uint64_t depth); + WidgetExtension* createWidget(const sol::table& layout, bool isRoot, uint64_t depth); void updateWidget(WidgetExtension* ext, const sol::table& layout, uint64_t depth); std::vector updateContent( @@ -130,7 +134,7 @@ namespace LuaUi sol::object child = content.at(i); if (child.is()) { - WidgetExtension* root = pluckElementRoot(child); + WidgetExtension* root = pluckElementRoot(child, depth); if (ext != root) destroyChild(ext); result[i] = root; @@ -145,7 +149,7 @@ namespace LuaUi else { destroyChild(ext); - ext = createWidget(newLayout, depth); + ext = createWidget(newLayout, false, depth); } result[i] = ext; } @@ -156,9 +160,9 @@ namespace LuaUi { sol::object child = content.at(i); if (child.is()) - result[i] = pluckElementRoot(child); + result[i] = pluckElementRoot(child, depth); else - result[i] = createWidget(child.as(), depth); + result[i] = createWidget(child.as(), false, depth); } return result; } @@ -191,7 +195,7 @@ namespace LuaUi }); } - WidgetExtension* createWidget(const sol::table& layout, uint64_t depth) + WidgetExtension* createWidget(const sol::table& layout, bool isRoot, uint64_t depth) { static auto widgetTypeMap = widgetTypeToName(); std::string type = widgetType(layout); @@ -205,7 +209,7 @@ namespace LuaUi WidgetExtension* ext = dynamic_cast(widget); if (!ext) throw std::runtime_error("Invalid widget!"); - ext->initialize(layout.lua_state(), widget, depth == 0); + ext->initialize(layout.lua_state(), widget, isRoot); updateWidget(ext, layout, depth); return ext; @@ -247,8 +251,7 @@ namespace LuaUi : mRoot(nullptr) , mLayout(std::move(layout)) , mLayer() - , mUpdate(false) - , mDestroy(false) + , mState(Element::New) { } @@ -267,12 +270,12 @@ namespace LuaUi sGameElements.erase(element); } - void Element::create() + void Element::create(uint64_t depth) { assert(!mRoot); - if (!mRoot) + if (mState == New) { - mRoot = createWidget(layout(), 0); + mRoot = createWidget(layout(), true, depth); mLayer = setLayer(mRoot, layout()); updateRootCoord(mRoot); } @@ -280,15 +283,16 @@ namespace LuaUi void Element::update() { - if (mRoot && mUpdate) + if (mState == Update) { + assert(mRoot); if (mRoot->widget()->getTypeName() != widgetType(layout())) { destroyRoot(mRoot); WidgetExtension* parent = mRoot->getParent(); auto children = parent->children(); auto it = std::find(children.begin(), children.end(), mRoot); - mRoot = createWidget(layout(), 0); + mRoot = createWidget(layout(), true, 0); assert(it != children.end()); *it = mRoot; parent->setChildren(children); @@ -301,16 +305,18 @@ namespace LuaUi mLayer = setLayer(mRoot, layout()); updateRootCoord(mRoot); } - mUpdate = false; + mState = Created; } void Element::destroy() { - if (mRoot) + if (mState != Destroyed) { destroyRoot(mRoot); mRoot = nullptr; - mLayout = sol::make_object(mLayout.lua_state(), sol::nil); + if (mState != New) + mLayout = sol::make_object(mLayout.lua_state(), sol::nil); + mState = Destroyed; } } } diff --git a/components/lua_ui/element.hpp b/components/lua_ui/element.hpp index 4398a769df..39a1fdd769 100644 --- a/components/lua_ui/element.hpp +++ b/components/lua_ui/element.hpp @@ -21,10 +21,18 @@ namespace LuaUi WidgetExtension* mRoot; sol::object mLayout; std::string mLayer; - bool mUpdate; - bool mDestroy; - void create(); + enum State + { + New, + Created, + Update, + Destroy, + Destroyed, + }; + State mState; + + void create(uint64_t dept = 0); void update(); From a11e553de4000052e5f03ef9acbe360f460d9581 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 6 Mar 2024 22:23:07 +0100 Subject: [PATCH 201/451] Optimize setting group rendering by rendering them as separate elements, support element-rendered setting renderers --- files/data/scripts/omw/settings/menu.lua | 70 +++++++++++++++++------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 2246b5ad7e..7d425f684b 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -6,8 +6,11 @@ local core = require('openmw.core') local storage = require('openmw.storage') local I = require('openmw.interfaces') +local auxUi = require('openmw_aux.ui') + local common = require('scripts.omw.settings.common') --- :reset on startup instead of :removeOnExit +common.getSection(false, common.groupSectionKey):setLifeTime(storage.LIFE_TIME.GameSession) +-- need to :reset() on reloadlua as well as game session end common.getSection(false, common.groupSectionKey):reset() local renderers = {} @@ -21,6 +24,7 @@ local interfaceL10n = core.l10n('Interface') local pages = {} local groups = {} local pageOptions = {} +local groupElements = {} local interval = { template = I.MWUI.templates.interval } local growingIntreval = { @@ -116,6 +120,11 @@ local function renderSetting(group, setting, value, global) } end local argument = common.getArgumentSection(global, group.key):get(setting.key) + local ok, rendererResult = pcall(renderFunction, value, set, argument) + if not ok then + print(string.format('Setting %s renderer "%s" error: %s', setting.key, setting.renderer, rendererResult)) + end + return { name = setting.key, type = ui.TYPE.Flex, @@ -129,7 +138,7 @@ local function renderSetting(group, setting, value, global) content = ui.content { titleLayout, growingIntreval, - renderFunction(value, set, argument), + ok and rendererResult or {}, -- TODO: display error? }, } end @@ -245,10 +254,12 @@ end local function generateSearchHints(page) local hints = {} - local l10n = core.l10n(page.l10n) - table.insert(hints, l10n(page.name)) - if page.description then - table.insert(hints, l10n(page.description)) + do + local l10n = core.l10n(page.l10n) + table.insert(hints, l10n(page.name)) + if page.description then + table.insert(hints, l10n(page.description)) + end end local pageGroups = groups[page.key] for _, pageGroup in pairs(pageGroups) do @@ -281,7 +292,15 @@ local function renderPage(page, options) if not group then error(string.format('%s group "%s" was not found', pageGroup.global and 'Global' or 'Player', pageGroup.key)) end - table.insert(groupLayouts, renderGroup(group, pageGroup.global)) + local groupElement = groupElements[page.key][group.key] + if not groupElement or not groupElement.layout then + groupElement = ui.create(renderGroup(group, pageGroup.global)) + end + if groupElement.layout == nil then + error(string.format('Destroyed group element for %s %s', page.key, group.key)) + end + groupElements[page.key][group.key] = groupElement + table.insert(groupLayouts, groupElement) end local groupsLayout = { name = 'groups', @@ -344,18 +363,23 @@ local function onSettingChanged(global) local group = common.getSection(global, common.groupSectionKey):get(groupKey) if not group or not pageOptions[group.page] then return end + local groupElement = groupElements[group.page][group.key] + if not settingKey then - renderPage(pages[group.page], pageOptions[group.page]) + if groupElement then + groupElement.layout = renderGroup(group) + groupElement:update() + else + renderPage(pages[group.page], pageOptions[group.page]) + end return end local value = common.getSection(global, group.key):get(settingKey) - local element = pageOptions[group.page].element - local groupsLayout = element.layout.content.groups - local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] - local settingsContent = groupLayout.content.settings.content - settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) - element:update() + local settingsContent = groupElement.layout.content.settings.content + auxUi.deepDestroy(settingsContent[settingKey]) -- support setting renderers which return UI elements + settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value) + groupElement:update() end) end @@ -364,6 +388,8 @@ local function onGroupRegistered(global, key) if not group then return end groups[group.page] = groups[group.page] or {} + groupElements[group.page] = groupElements[group.page] or {} + local pageGroup = { key = group.key, global = global, @@ -380,11 +406,9 @@ local function onGroupRegistered(global, key) local value = common.getSection(global, group.key):get(settingKey) - local element = pageOptions[group.page].element - local groupsLayout = element.layout.content.groups - local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)] - local settingsContent = groupLayout.content.settings.content - settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) + local element = groupElements[group.page][group.key] + local settingsContent = element.layout.content.settings.content + settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value) element:update() end)) end @@ -422,6 +446,11 @@ local function resetPlayerGroups() for pageKey, page in pairs(groups) do for groupKey in pairs(page) do if not menuGroups[groupKey] then + if groupElements[pageKey][groupKey] then + groupElements[pageKey][groupKey]:destroy() + print(string.format('destroyed group element %s %s', pageKey, groupKey)) + groupElements[pageKey][groupKey] = nil + end page[groupKey] = nil playerGroupsSection:set(groupKey, nil) end @@ -430,7 +459,8 @@ local function resetPlayerGroups() if options then if not menuPages[pageKey] then if options.element then - options.element:destroy() + auxUi.deepDestroy(options.element) + options.element = nil end ui.removeSettingsPage(options) pageOptions[pageKey] = nil From d8c74e1d6267cc3af3885ea03f2fea18bf20ec26 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 6 Mar 2024 23:22:03 +0000 Subject: [PATCH 202/451] conf.py: Set navigation_with_keys to allow navigating documentation through arrow keys --- docs/source/conf.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 902e84c393..1dca7374e5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -143,7 +143,9 @@ html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +html_theme_options = { + 'navigation_with_keys': True +} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] From ed23f487543181c70960bad5421740d131a4b729 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 6 Mar 2024 23:44:24 +0000 Subject: [PATCH 203/451] Actually erase the things we're removing Caused by bad copy and paste --- components/config/gamesettings.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index 96e0864a9e..02cf9f9b8b 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -76,8 +76,7 @@ namespace Config if (!existingDir.isEmpty()) { // non-user settings can't be removed as we can't edit the openmw.cfg they're in - std::remove_if(mDataDirs.begin(), mDataDirs.end(), - [&](const SettingValue& dir) { return isUserSetting(dir) && dir.value == existingDir; }); + mDataDirs.erase(std::remove_if(mDataDirs.begin(), mDataDirs.end(), [&](const SettingValue& dir) { return isUserSetting(dir) && dir.value == existingDir; }), mDataDirs.end()); } } From 243b5b6666f9ccdc024f90a7f4a933b8d1e87e8e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 6 Mar 2024 23:52:16 +0000 Subject: [PATCH 204/451] Hopefully convince the old MSVC version on GitLab CI to work The old code was legal, and the things it did worked in other places, so should have worked here, too. Hopefully just rearranging stuff convinces what I assume to be a compiler bug to not happen. --- components/config/gamesettings.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 9aed4656bc..976d5e20f2 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -544,19 +544,15 @@ bool Config::GameSettings::hasMaster() void Config::GameSettings::setContentList( const QList& dirNames, const QList& archiveNames, const QStringList& fileNames) { - auto const reset = [this](const char* key, const QStringList& list) { - remove(key); - for (auto const& item : list) - setMultiValue(key, { item }); - }; - remove(sDirectoryKey); for (auto const& item : dirNames) setMultiValue(sDirectoryKey, item); remove(sArchiveKey); for (auto const& item : archiveNames) setMultiValue(sArchiveKey, item); - reset(sContentKey, fileNames); + remove(sContentKey); + for (auto const& item : fileNames) + setMultiValue(sContentKey, { item }); } QList Config::GameSettings::getDataDirs() const From 36f5c819bbb228becb50c57e2ede1949d27ded5f Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 7 Mar 2024 01:48:16 +0000 Subject: [PATCH 205/451] capitulate --- components/config/gamesettings.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index 02cf9f9b8b..d23f225eb0 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -76,7 +76,10 @@ namespace Config if (!existingDir.isEmpty()) { // non-user settings can't be removed as we can't edit the openmw.cfg they're in - mDataDirs.erase(std::remove_if(mDataDirs.begin(), mDataDirs.end(), [&](const SettingValue& dir) { return isUserSetting(dir) && dir.value == existingDir; }), mDataDirs.end()); + mDataDirs.erase( + std::remove_if(mDataDirs.begin(), mDataDirs.end(), + [&](const SettingValue& dir) { return isUserSetting(dir) && dir.value == existingDir; }), + mDataDirs.end()); } } From af8c2a94dfbc9292f014587eb8c2031a13a62a2c Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 18 Feb 2024 21:50:19 +0000 Subject: [PATCH 206/451] Fix: hardcoded weather meshes, use settings instead Signed-off-by: Sam Hellawell --- apps/openmw/mwworld/weather.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 36b5958dc3..58fea640f6 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -45,7 +46,7 @@ namespace MWWorld osg::Vec3f calculateStormDirection(const std::string& particleEffect) { osg::Vec3f stormDirection = MWWorld::Weather::defaultDirection(); - if (particleEffect == "meshes\\ashcloud.nif" || particleEffect == "meshes\\blightcloud.nif") + if (particleEffect == Settings::models().mWeatherashcloud.get() || particleEffect == Settings::models().mWeatherblightcloud.get()) { osg::Vec3f playerPos = MWMechanics::getPlayer().getRefData().getPosition().asVec3(); playerPos.z() = 0; @@ -581,10 +582,10 @@ namespace MWWorld addWeather("Overcast", 0.7f, 0.0f); // 3 addWeather("Rain", 0.5f, 10.0f); // 4 addWeather("Thunderstorm", 0.5f, 20.0f); // 5 - addWeather("Ashstorm", 0.2f, 50.0f, "meshes\\ashcloud.nif"); // 6 - addWeather("Blight", 0.2f, 60.0f, "meshes\\blightcloud.nif"); // 7 - addWeather("Snow", 0.5f, 40.0f, "meshes\\snow.nif"); // 8 - addWeather("Blizzard", 0.16f, 70.0f, "meshes\\blizzard.nif"); // 9 + addWeather("Ashstorm", 0.2f, 50.0f, Settings::models().mWeatherashcloud.get()); // 6 + addWeather("Blight", 0.2f, 60.0f, Settings::models().mWeatherblightcloud.get()); // 7 + addWeather("Snow", 0.5f, 40.0f, Settings::models().mWeathersnow.get()); // 8 + addWeather("Blizzard", 0.16f, 70.0f, Settings::models().mWeatherblizzard.get()); // 9 Store::iterator it = store.get().begin(); for (; it != store.get().end(); ++it) @@ -720,7 +721,7 @@ namespace MWWorld // For some reason Ash Storm is not considered as a precipitation weather in game mPrecipitation = !(mResult.mParticleEffect.empty() && mResult.mRainEffect.empty()) - && mResult.mParticleEffect != "meshes\\ashcloud.nif"; + && mResult.mParticleEffect != Settings::models().mWeatherashcloud.get(); mStormDirection = calculateStormDirection(mResult.mParticleEffect); mRendering.getSkyManager()->setStormParticleDirection(mStormDirection); From f28b3f660148ae73ddc2b3f7293b3ea60c8b466f Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 18 Feb 2024 21:51:48 +0000 Subject: [PATCH 207/451] Style tweak Signed-off-by: Sam Hellawell --- apps/openmw/mwworld/weather.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 58fea640f6..6cca2a8cc1 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -1,8 +1,9 @@ #include "weather.hpp" +#include + #include -#include #include #include #include From bf7819f71d9a75b8b2b532dc930d7984fb6d514a Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 18 Feb 2024 22:06:09 +0000 Subject: [PATCH 208/451] fix clang format --- apps/openmw/mwworld/weather.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 6cca2a8cc1..4f6f52a81a 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -47,7 +47,8 @@ namespace MWWorld osg::Vec3f calculateStormDirection(const std::string& particleEffect) { osg::Vec3f stormDirection = MWWorld::Weather::defaultDirection(); - if (particleEffect == Settings::models().mWeatherashcloud.get() || particleEffect == Settings::models().mWeatherblightcloud.get()) + if (particleEffect == Settings::models().mWeatherashcloud.get() + || particleEffect == Settings::models().mWeatherblightcloud.get()) { osg::Vec3f playerPos = MWMechanics::getPlayer().getRefData().getPosition().asVec3(); playerPos.z() = 0; From c6ee01b0bee074598e3acd86e2b14d4b7f8a27d3 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 7 Mar 2024 04:49:48 +0000 Subject: [PATCH 209/451] Apply fix to sky manager Signed-off-by: Sam Hellawell --- apps/openmw/mwrender/sky.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 9c8b0658a9..231f90fd78 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -528,7 +528,7 @@ namespace MWRender if (hasRain()) return mRainRipplesEnabled; - if (mParticleNode && mCurrentParticleEffect == "meshes\\snow.nif") + if (mParticleNode && mCurrentParticleEffect == Settings::models().mWeathersnow.get()) return mSnowRipplesEnabled; return false; @@ -554,7 +554,7 @@ namespace MWRender osg::Quat quat; quat.makeRotate(MWWorld::Weather::defaultDirection(), mStormParticleDirection); // Morrowind deliberately rotates the blizzard mesh, so so should we. - if (mCurrentParticleEffect == "meshes\\blizzard.nif") + if (mCurrentParticleEffect == Settings::models().mWeatherblizzard.get()) quat.makeRotate(osg::Vec3f(-1, 0, 0), mStormParticleDirection); mParticleNode->setAttitude(quat); } @@ -726,7 +726,7 @@ namespace MWRender const osg::Vec3 defaultWrapRange = osg::Vec3(1024, 1024, 800); const bool occlusionEnabledForEffect - = !mRainEffect.empty() || mCurrentParticleEffect == "meshes\\snow.nif"; + = !mRainEffect.empty() || mCurrentParticleEffect == Settings::models().mWeathersnow.get(); for (unsigned int i = 0; i < findPSVisitor.mFoundNodes.size(); ++i) { From 5dcac4c48f54e5666df8774f7d685abd7d8a75ce Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 7 Mar 2024 15:43:35 +0400 Subject: [PATCH 210/451] Do not treat Alt-Tab as resolution change (bug 7866) --- CHANGELOG.md | 1 + apps/openmw/mwgui/windowmanagerimp.cpp | 3 +++ components/sdlutil/sdlinputwrapper.cpp | 5 +++++ 3 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 953801b345..ddbab574d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -157,6 +157,7 @@ Bug #7840: First run of the launcher doesn't save viewing distance as the default value Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs Bug #7859: AutoCalc flag is not used to calculate potion value + Bug #7866: Alt-tabbing is considered as a resolution change Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index e463443b0c..4a87b38324 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1172,6 +1172,9 @@ namespace MWGui void WindowManager::windowResized(int x, int y) { + if (x == Settings::video().mResolutionX && y == Settings::video().mResolutionY) + return; + Settings::video().mResolutionX.set(x); Settings::video().mResolutionY.set(y); diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index cc9706732e..43de84bb70 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -252,6 +252,11 @@ namespace SDLUtil SDL_GL_GetDrawableSize(mSDLWindow, &w, &h); int x, y; SDL_GetWindowPosition(mSDLWindow, &x, &y); + + // Happens when you Alt-Tab out of game + if (w == 0 && h == 0) + return; + mViewer->getCamera()->getGraphicsContext()->resized(x, y, w, h); mViewer->getEventQueue()->windowResize(x, y, w, h); From b055367b3b5ac2a837ac4699809412eb258c0b22 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 7 Mar 2024 21:36:21 +0100 Subject: [PATCH 211/451] Track map position using MWWorld::Cell --- apps/openmw/mwbase/windowmanager.hpp | 3 - apps/openmw/mwgui/mapwindow.cpp | 92 ++++++++++++++------------ apps/openmw/mwgui/mapwindow.hpp | 9 +-- apps/openmw/mwgui/windowmanagerimp.cpp | 16 ++--- apps/openmw/mwgui/windowmanagerimp.hpp | 7 +- apps/openmw/mwworld/cell.cpp | 2 + 6 files changed, 63 insertions(+), 66 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index a7859ad9e6..c252e0c490 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -202,9 +202,6 @@ namespace MWBase virtual bool getFullHelp() const = 0; - virtual void setActiveMap(int x, int y, bool interior) = 0; - ///< set the indices of the map texture that should be used - /// sets the visibility of the drowning bar virtual void setDrowningBarVisibility(bool visible) = 0; diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index cb6ba79f9e..ae6da7766f 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -95,6 +95,13 @@ namespace return std::clamp( viewingDistanceInCells, Constants::CellGridRadius, Settings::map().mMaxLocalViewingDistance.get()); } + + ESM::RefId getCellIdInWorldSpace(const MWWorld::Cell* cell, int x, int y) + { + if (cell->isExterior()) + return ESM::Cell::generateIdForCell(true, {}, x, y); + return cell->getId(); + } } namespace MWGui @@ -170,12 +177,9 @@ namespace MWGui LocalMapBase::LocalMapBase( CustomMarkerCollection& markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled) : mLocalMapRender(localMapRender) - , mCurX(0) - , mCurY(0) - , mInterior(false) + , mActiveCell(nullptr) , mLocalMap(nullptr) , mCompass(nullptr) - , mChanged(true) , mFogOfWarToggled(true) , mFogOfWarEnabled(fogOfWarEnabled) , mNumCells(1) @@ -231,12 +235,6 @@ namespace MWGui } } - void LocalMapBase::setCellPrefix(const std::string& prefix) - { - mPrefix = prefix; - mChanged = true; - } - bool LocalMapBase::toggleFogOfWar() { mFogOfWarToggled = !mFogOfWarToggled; @@ -262,8 +260,9 @@ namespace MWGui { // normalized cell coordinates auto mapWidgetSize = getWidgetSize(); - return MyGUI::IntPoint(std::round(nX * mapWidgetSize + (mCellDistance + (cellX - mCurX)) * mapWidgetSize), - std::round(nY * mapWidgetSize + (mCellDistance - (cellY - mCurY)) * mapWidgetSize)); + return MyGUI::IntPoint( + std::round(nX * mapWidgetSize + (mCellDistance + (cellX - mActiveCell->getGridX())) * mapWidgetSize), + std::round(nY * mapWidgetSize + (mCellDistance - (cellY - mActiveCell->getGridY())) * mapWidgetSize)); } MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) const @@ -272,7 +271,7 @@ namespace MWGui // normalized cell coordinates float nX, nY; - if (!mInterior) + if (mActiveCell->isExterior()) { ESM::ExteriorCellLocation cellPos = ESM::positionToExteriorCellLocation(worldX, worldY); cellIndex.x() = cellPos.mX; @@ -336,7 +335,7 @@ namespace MWGui std::vector& LocalMapBase::currentDoorMarkersWidgets() { - return mInterior ? mInteriorDoorMarkerWidgets : mExteriorDoorMarkerWidgets; + return mActiveCell->isExterior() ? mExteriorDoorMarkerWidgets : mInteriorDoorMarkerWidgets; } void LocalMapBase::updateCustomMarkers() @@ -344,12 +343,14 @@ namespace MWGui for (MyGUI::Widget* widget : mCustomMarkerWidgets) MyGUI::Gui::getInstance().destroyWidget(widget); mCustomMarkerWidgets.clear(); - + if (!mActiveCell) + return; for (int dX = -mCellDistance; dX <= mCellDistance; ++dX) { for (int dY = -mCellDistance; dY <= mCellDistance; ++dY) { - ESM::RefId cellRefId = ESM::Cell::generateIdForCell(!mInterior, mPrefix, mCurX + dX, mCurY + dY); + ESM::RefId cellRefId + = getCellIdInWorldSpace(mActiveCell, mActiveCell->getGridX() + dX, mActiveCell->getGridY() + dY); CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellRefId); for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; @@ -377,16 +378,25 @@ namespace MWGui redraw(); } - void LocalMapBase::setActiveCell(const int x, const int y, bool interior) + void LocalMapBase::setActiveCell(const MWWorld::Cell& cell) { - if (x == mCurX && y == mCurY && mInterior == interior && !mChanged) + if (&cell == mActiveCell) return; // don't do anything if we're still in the same cell - if (!interior && !(x == mCurX && y == mCurY)) + const int x = cell.getGridX(); + const int y = cell.getGridY(); + + if (cell.isExterior()) { - const MyGUI::IntRect intersection - = { std::max(x, mCurX) - mCellDistance, std::max(y, mCurY) - mCellDistance, - std::min(x, mCurX) + mCellDistance, std::min(y, mCurY) + mCellDistance }; + int curX = 0; + int curY = 0; + if (mActiveCell) + { + curX = mActiveCell->getGridX(); + curY = mActiveCell->getGridY(); + } + const MyGUI::IntRect intersection = { std::max(x, curX) - mCellDistance, std::max(y, curY) - mCellDistance, + std::min(x, curX) + mCellDistance, std::min(y, curY) + mCellDistance }; const MyGUI::IntRect activeGrid = createRect({ x, y }, Constants::CellGridRadius); const MyGUI::IntRect currentView = createRect({ x, y }, mCellDistance); @@ -407,17 +417,14 @@ namespace MWGui for (auto& widget : mDoorMarkersToRecycle) widget->setVisible(false); - for (auto const& cell : mMaps) + for (auto const& entry : mMaps) { - if (mHasALastActiveCell && !intersection.inside({ cell.mCellX, cell.mCellY })) - mLocalMapRender->removeExteriorCell(cell.mCellX, cell.mCellY); + if (mHasALastActiveCell && !intersection.inside({ entry.mCellX, entry.mCellY })) + mLocalMapRender->removeExteriorCell(entry.mCellX, entry.mCellY); } } - mCurX = x; - mCurY = y; - mInterior = interior; - mChanged = false; + mActiveCell = &cell; for (int mx = 0; mx < mNumCells; ++mx) { @@ -441,7 +448,7 @@ namespace MWGui for (MyGUI::Widget* widget : currentDoorMarkersWidgets()) widget->setCoord(getMarkerCoordinates(widget, 8)); - if (!mInterior) + if (mActiveCell->isExterior()) mHasALastActiveCell = true; updateMagicMarkers(); @@ -580,7 +587,7 @@ namespace MWGui if (!entry.mMapTexture) { - if (!mInterior) + if (mActiveCell->isExterior()) requestMapRender(&MWBase::Environment::get().getWorldModel()->getExterior( ESM::ExteriorCellLocation(entry.mCellX, entry.mCellY, ESM::Cell::sDefaultWorldspaceId))); @@ -626,12 +633,12 @@ namespace MWGui mDoorMarkersToRecycle.end(), mInteriorDoorMarkerWidgets.begin(), mInteriorDoorMarkerWidgets.end()); mInteriorDoorMarkerWidgets.clear(); - if (mInterior) + if (!mActiveCell->isExterior()) { for (MyGUI::Widget* widget : mExteriorDoorMarkerWidgets) widget->setVisible(false); - MWWorld::CellStore& cell = worldModel->getInterior(mPrefix); + MWWorld::CellStore& cell = worldModel->getInterior(mActiveCell->getNameId()); world->getDoorMarkers(cell, doors); } else @@ -678,7 +685,7 @@ namespace MWGui } currentDoorMarkersWidgets().push_back(markerWidget); - if (!mInterior) + if (mActiveCell->isExterior()) mExteriorDoorsByCell[{ data->cellX, data->cellY }].push_back(markerWidget); } @@ -701,8 +708,7 @@ namespace MWGui MWWorld::CellStore* markedCell = nullptr; ESM::Position markedPosition; MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); - if (markedCell && markedCell->isExterior() == !mInterior - && (!mInterior || Misc::StringUtils::ciEqual(markedCell->getCell()->getNameId(), mPrefix))) + if (markedCell && markedCell->getCell()->getWorldSpace() == mActiveCell->getWorldSpace()) { MarkerUserData markerPos(mLocalMapRender); MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", @@ -870,11 +876,11 @@ namespace MWGui int y = (int(widgetPos.top / float(mapWidgetSize)) - mCellDistance) * -1; float nX = widgetPos.left / float(mapWidgetSize) - int(widgetPos.left / float(mapWidgetSize)); float nY = widgetPos.top / float(mapWidgetSize) - int(widgetPos.top / float(mapWidgetSize)); - x += mCurX; - y += mCurY; + x += mActiveCell->getGridX(); + y += mActiveCell->getGridY(); osg::Vec2f worldPos; - if (mInterior) + if (!mActiveCell->isExterior()) { worldPos = mLocalMapRender->interiorMapToWorldPosition(nX, nY, x, y); } @@ -886,7 +892,7 @@ namespace MWGui mEditingMarker.mWorldX = worldPos.x(); mEditingMarker.mWorldY = worldPos.y(); - ESM::RefId clickedId = ESM::Cell::generateIdForCell(!mInterior, LocalMapBase::mPrefix, x, y); + ESM::RefId clickedId = getCellIdInWorldSpace(mActiveCell, x, y); mEditingMarker.mCell = clickedId; @@ -977,7 +983,7 @@ namespace MWGui resizeGlobalMap(); float x = mCurPos.x(), y = mCurPos.y(); - if (mInterior) + if (!mActiveCell->isExterior()) { auto pos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); x = pos.x(); @@ -1020,7 +1026,7 @@ namespace MWGui resizeGlobalMap(); } - MapWindow::~MapWindow() {} + MapWindow::~MapWindow() = default; void MapWindow::setCellName(const std::string& cellName) { @@ -1289,7 +1295,7 @@ namespace MWGui mMarkers.clear(); mGlobalMapRender->clear(); - mChanged = true; + mActiveCell = nullptr; for (auto& widgetPair : mGlobalMapMarkers) MyGUI::Gui::getInstance().destroyWidget(widgetPair.first.widget); diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 29759a4365..8066256437 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -27,6 +27,7 @@ namespace ESM namespace MWWorld { + class Cell; class CellStore; } @@ -77,8 +78,7 @@ namespace MWGui virtual ~LocalMapBase(); void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int cellDistance = Constants::CellGridRadius); - void setCellPrefix(const std::string& prefix); - void setActiveCell(const int x, const int y, bool interior = false); + void setActiveCell(const MWWorld::Cell& cell); void requestMapRender(const MWWorld::CellStore* cell); void setPlayerDir(const float x, const float y); void setPlayerPos(int cellX, int cellY, const float nx, const float ny); @@ -115,15 +115,12 @@ namespace MWGui float mLocalMapZoom = 1.f; MWRender::LocalMap* mLocalMapRender; - int mCurX, mCurY; // the position of the active cell on the global map (in cell coords) + const MWWorld::Cell* mActiveCell; bool mHasALastActiveCell = false; osg::Vec2f mCurPos; // the position of the player in the world (in cell coords) - bool mInterior; MyGUI::ScrollView* mLocalMap; MyGUI::ImageBox* mCompass; - std::string mPrefix; - bool mChanged; bool mFogOfWarToggled; bool mFogOfWarEnabled; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index e463443b0c..29b9cb0e84 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -844,7 +844,7 @@ namespace MWGui if (!player.getCell()->isExterior()) { - setActiveMap(x, y, true); + setActiveMap(*player.getCell()->getCell()); } // else: need to know the current grid center, call setActiveMap from changeCell @@ -982,29 +982,23 @@ namespace MWGui mMap->addVisitedLocation(name, cellCommon->getGridX(), cellCommon->getGridY()); mMap->cellExplored(cellCommon->getGridX(), cellCommon->getGridY()); - - setActiveMap(cellCommon->getGridX(), cellCommon->getGridY(), false); } else { - mMap->setCellPrefix(std::string(cellCommon->getNameId())); - mHud->setCellPrefix(std::string(cellCommon->getNameId())); - osg::Vec3f worldPos; if (!MWBase::Environment::get().getWorld()->findInteriorPositionInWorldSpace(cell, worldPos)) worldPos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); else MWBase::Environment::get().getWorld()->getPlayer().setLastKnownExteriorPosition(worldPos); mMap->setGlobalMapPlayerPosition(worldPos.x(), worldPos.y()); - - setActiveMap(0, 0, true); } + setActiveMap(*cellCommon); } - void WindowManager::setActiveMap(int x, int y, bool interior) + void WindowManager::setActiveMap(const MWWorld::Cell& cell) { - mMap->setActiveCell(x, y, interior); - mHud->setActiveCell(x, y, interior); + mMap->setActiveCell(cell); + mHud->setActiveCell(cell); } void WindowManager::setDrowningBarVisibility(bool visible) diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index d6a286632c..3445ebdb9a 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -50,6 +50,7 @@ namespace MyGUI namespace MWWorld { + class Cell; class ESMStore; } @@ -216,9 +217,6 @@ namespace MWGui bool toggleFullHelp() override; ///< show extra info in item tooltips (owner, script) bool getFullHelp() const override; - void setActiveMap(int x, int y, bool interior) override; - ///< set the indices of the map texture that should be used - /// sets the visibility of the drowning bar void setDrowningBarVisibility(bool visible) override; @@ -589,6 +587,9 @@ namespace MWGui void setCullMask(uint32_t mask) override; uint32_t getCullMask() override; + void setActiveMap(const MWWorld::Cell& cell); + ///< set the indices of the map texture that should be used + Files::ConfigurationManager& mCfgMgr; }; } diff --git a/apps/openmw/mwworld/cell.cpp b/apps/openmw/mwworld/cell.cpp index 56afc104cf..1bd9761f72 100644 --- a/apps/openmw/mwworld/cell.cpp +++ b/apps/openmw/mwworld/cell.cpp @@ -100,6 +100,8 @@ namespace MWWorld mWaterHeight = -1.f; mHasWater = true; } + else + mGridPos = {}; } ESM::RefId Cell::getWorldSpace() const From 5859fd464cc123e7c813268f26b99505017ea482 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 8 Mar 2024 01:22:42 +0100 Subject: [PATCH 212/451] Add option to disable precompiled headers To be able to use ccache. Also fix compilation errors appeared due to absence of precompiled headers. --- CMakeLists.txt | 1 + apps/benchmarks/detournavigator/CMakeLists.txt | 2 +- apps/benchmarks/esm/CMakeLists.txt | 2 +- apps/benchmarks/settings/CMakeLists.txt | 2 +- apps/bsatool/CMakeLists.txt | 2 +- apps/bulletobjecttool/CMakeLists.txt | 2 +- apps/esmtool/CMakeLists.txt | 2 +- apps/essimporter/CMakeLists.txt | 2 +- apps/launcher/CMakeLists.txt | 2 +- apps/mwiniimporter/CMakeLists.txt | 2 +- apps/navmeshtool/CMakeLists.txt | 2 +- apps/niftest/CMakeLists.txt | 2 +- apps/opencs/CMakeLists.txt | 2 +- apps/opencs_tests/CMakeLists.txt | 2 +- apps/openmw/CMakeLists.txt | 2 +- apps/openmw_test_suite/CMakeLists.txt | 2 +- components/CMakeLists.txt | 2 +- components/detournavigator/commulativeaabb.hpp | 2 +- components/esm/esm3exteriorcellrefid.hpp | 2 +- components/esm/format.cpp | 1 + components/esm/generatedrefid.hpp | 1 + components/platform/platform.cpp | 5 ++++- extern/Base64/CMakeLists.txt | 2 +- extern/CMakeLists.txt | 2 +- extern/oics/CMakeLists.txt | 2 +- extern/osg-ffmpeg-videoplayer/CMakeLists.txt | 2 +- 26 files changed, 29 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index acec38fad0..50af98393e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ option(BUILD_BENCHMARKS "Build benchmarks with Google Benchmark" OFF) option(BUILD_NAVMESHTOOL "Build navmesh tool" ON) option(BUILD_BULLETOBJECTTOOL "Build Bullet object tool" ON) option(BUILD_OPENCS_TESTS "Build OpenMW Construction Set tests" OFF) +option(PRECOMPILE_HEADERS_WITH_MSVC "Precompile most common used headers with MSVC (alternative to ccache)" ON) set(OpenGL_GL_PREFERENCE LEGACY) # Use LEGACY as we use GL2; GLNVD is for GL3 and up. diff --git a/apps/benchmarks/detournavigator/CMakeLists.txt b/apps/benchmarks/detournavigator/CMakeLists.txt index 2b3a6abe51..ffe7818a5a 100644 --- a/apps/benchmarks/detournavigator/CMakeLists.txt +++ b/apps/benchmarks/detournavigator/CMakeLists.txt @@ -5,7 +5,7 @@ if (UNIX AND NOT APPLE) target_link_libraries(openmw_detournavigator_navmeshtilescache_benchmark ${CMAKE_THREAD_LIBS_INIT}) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw_detournavigator_navmeshtilescache_benchmark PRIVATE ) endif() diff --git a/apps/benchmarks/esm/CMakeLists.txt b/apps/benchmarks/esm/CMakeLists.txt index 74870ceda1..9b5afd649d 100644 --- a/apps/benchmarks/esm/CMakeLists.txt +++ b/apps/benchmarks/esm/CMakeLists.txt @@ -5,7 +5,7 @@ if (UNIX AND NOT APPLE) target_link_libraries(openmw_esm_refid_benchmark ${CMAKE_THREAD_LIBS_INIT}) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw_esm_refid_benchmark PRIVATE ) endif() diff --git a/apps/benchmarks/settings/CMakeLists.txt b/apps/benchmarks/settings/CMakeLists.txt index ccdd51eeac..51e2d2b0fd 100644 --- a/apps/benchmarks/settings/CMakeLists.txt +++ b/apps/benchmarks/settings/CMakeLists.txt @@ -8,7 +8,7 @@ if (UNIX AND NOT APPLE) target_link_libraries(openmw_settings_access_benchmark ${CMAKE_THREAD_LIBS_INIT}) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw_settings_access_benchmark PRIVATE ) endif() diff --git a/apps/bsatool/CMakeLists.txt b/apps/bsatool/CMakeLists.txt index a567499ac6..e893feb91a 100644 --- a/apps/bsatool/CMakeLists.txt +++ b/apps/bsatool/CMakeLists.txt @@ -18,7 +18,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(bsatool gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(bsatool PRIVATE diff --git a/apps/bulletobjecttool/CMakeLists.txt b/apps/bulletobjecttool/CMakeLists.txt index 6e6e1cdbb3..d9bba10195 100644 --- a/apps/bulletobjecttool/CMakeLists.txt +++ b/apps/bulletobjecttool/CMakeLists.txt @@ -19,7 +19,7 @@ if (WIN32) install(TARGETS openmw-bulletobjecttool RUNTIME DESTINATION ".") endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-bulletobjecttool PRIVATE diff --git a/apps/esmtool/CMakeLists.txt b/apps/esmtool/CMakeLists.txt index 6f7fa1a993..d26e2a2128 100644 --- a/apps/esmtool/CMakeLists.txt +++ b/apps/esmtool/CMakeLists.txt @@ -25,7 +25,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(esmtool gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(esmtool PRIVATE diff --git a/apps/essimporter/CMakeLists.txt b/apps/essimporter/CMakeLists.txt index c6c98791e3..5dfb747aee 100644 --- a/apps/essimporter/CMakeLists.txt +++ b/apps/essimporter/CMakeLists.txt @@ -47,7 +47,7 @@ if (WIN32) INSTALL(TARGETS openmw-essimporter RUNTIME DESTINATION ".") endif(WIN32) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-essimporter PRIVATE diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 0c888afe9d..bd6a7062fd 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -94,7 +94,7 @@ if(USE_QT) set_property(TARGET openmw-launcher PROPERTY AUTOMOC ON) endif(USE_QT) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-launcher PRIVATE diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index 704393cd0d..49be8309ab 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -33,7 +33,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(openmw-iniimporter gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-iniimporter PRIVATE diff --git a/apps/navmeshtool/CMakeLists.txt b/apps/navmeshtool/CMakeLists.txt index 9abd8dc283..13c8230abd 100644 --- a/apps/navmeshtool/CMakeLists.txt +++ b/apps/navmeshtool/CMakeLists.txt @@ -21,7 +21,7 @@ if (WIN32) install(TARGETS openmw-navmeshtool RUNTIME DESTINATION ".") endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-navmeshtool PRIVATE diff --git a/apps/niftest/CMakeLists.txt b/apps/niftest/CMakeLists.txt index f112e087e3..cf37162f6e 100644 --- a/apps/niftest/CMakeLists.txt +++ b/apps/niftest/CMakeLists.txt @@ -17,6 +17,6 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(niftest gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(niftest PRIVATE ) endif() diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 9bf02e10c9..c2f249171a 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -292,7 +292,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(openmw-cs-lib gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-cs-lib PRIVATE diff --git a/apps/opencs_tests/CMakeLists.txt b/apps/opencs_tests/CMakeLists.txt index 2b7309f8b9..3bf783bb68 100644 --- a/apps/opencs_tests/CMakeLists.txt +++ b/apps/opencs_tests/CMakeLists.txt @@ -26,7 +26,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(openmw-cs-tests PRIVATE gcov) endif() -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw-cs-tests PRIVATE ) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 5fb06881ec..5fb4f398f1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -161,7 +161,7 @@ target_link_libraries(openmw components ) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw PRIVATE diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 71da2de590..3fe76623bf 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -126,7 +126,7 @@ target_compile_definitions(openmw_test_suite PRIVATE OPENMW_DATA_DIR=u8"${CMAKE_CURRENT_BINARY_DIR}/data" OPENMW_PROJECT_SOURCE_DIR=u8"${PROJECT_SOURCE_DIR}") -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(openmw_test_suite PRIVATE diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 8bdead1357..1c553e855e 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -691,7 +691,7 @@ if(USE_QT) set_property(TARGET components_qt PROPERTY AUTOMOC ON) endif(USE_QT) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(components PUBLIC diff --git a/components/detournavigator/commulativeaabb.hpp b/components/detournavigator/commulativeaabb.hpp index 5d24c329ca..46cf64b348 100644 --- a/components/detournavigator/commulativeaabb.hpp +++ b/components/detournavigator/commulativeaabb.hpp @@ -3,7 +3,7 @@ #include -#include +#include namespace DetourNavigator { diff --git a/components/esm/esm3exteriorcellrefid.hpp b/components/esm/esm3exteriorcellrefid.hpp index 5fca8dc597..fd6a9b128d 100644 --- a/components/esm/esm3exteriorcellrefid.hpp +++ b/components/esm/esm3exteriorcellrefid.hpp @@ -3,7 +3,7 @@ #include #include - +#include #include #include diff --git a/components/esm/format.cpp b/components/esm/format.cpp index aa869ab998..04edc5c7db 100644 --- a/components/esm/format.cpp +++ b/components/esm/format.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace ESM { diff --git a/components/esm/generatedrefid.hpp b/components/esm/generatedrefid.hpp index c5cd1bcef5..e9d07ff314 100644 --- a/components/esm/generatedrefid.hpp +++ b/components/esm/generatedrefid.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace ESM { diff --git a/components/platform/platform.cpp b/components/platform/platform.cpp index 787cfa522e..9743c14337 100644 --- a/components/platform/platform.cpp +++ b/components/platform/platform.cpp @@ -1,12 +1,15 @@ #include "platform.hpp" +#ifdef WIN32 +#include +#endif + namespace Platform { static void increaseFileHandleLimit() { #ifdef WIN32 -#include // Increase limit for open files at the stream I/O level, see // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmaxstdio?view=msvc-170#remarks _setmaxstdio(8192); diff --git a/extern/Base64/CMakeLists.txt b/extern/Base64/CMakeLists.txt index 94992a22b5..fc750823c7 100644 --- a/extern/Base64/CMakeLists.txt +++ b/extern/Base64/CMakeLists.txt @@ -1,6 +1,6 @@ add_library(Base64 INTERFACE) target_include_directories(Base64 INTERFACE .) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(Base64 INTERFACE ) endif() diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 4a2cf1162b..1a6fcf2625 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -234,7 +234,7 @@ if (NOT OPENMW_USE_SYSTEM_YAML_CPP) ) FetchContent_MakeAvailableExcludeFromAll(yaml-cpp) - if (MSVC) + if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(yaml-cpp PRIVATE ) endif() endif() diff --git a/extern/oics/CMakeLists.txt b/extern/oics/CMakeLists.txt index 4bd3bc51ad..2d34f3f3e6 100644 --- a/extern/oics/CMakeLists.txt +++ b/extern/oics/CMakeLists.txt @@ -22,6 +22,6 @@ endif() target_link_libraries(oics SDL2::SDL2) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(oics PUBLIC ) endif() diff --git a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt index 10c8d356a0..8ff608bf04 100644 --- a/extern/osg-ffmpeg-videoplayer/CMakeLists.txt +++ b/extern/osg-ffmpeg-videoplayer/CMakeLists.txt @@ -18,7 +18,7 @@ target_link_libraries(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} SDL2::SDL2) link_directories(${CMAKE_CURRENT_BINARY_DIR}) -if (MSVC) +if (MSVC AND PRECOMPILE_HEADERS_WITH_MSVC) target_precompile_headers(${OSG_FFMPEG_VIDEOPLAYER_LIBRARY} PUBLIC From e0b13f0858309e328d512d056de4beda37171952 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 8 Mar 2024 01:44:47 +0000 Subject: [PATCH 213/451] Ensure default config values are present Moving builtin.omwscripts out of the root openmw.cfg means we actually might need to use the defaults, so need to have some. --- components/files/configurationmanager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 10e10375bb..7b4cbac864 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -68,6 +68,9 @@ namespace Files bool silent = mSilent; mSilent = quiet; + // ensure defaults are present + bpo::store(bpo::parsed_options(&description), variables); + std::optional config = loadConfig(mFixedPath.getLocalPath(), description); if (config) mActiveConfigPaths.push_back(mFixedPath.getLocalPath()); From baab28440e4283d43a5c42c15fe6484f7e0d4ff3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 8 Mar 2024 01:49:23 +0000 Subject: [PATCH 214/451] Russian translations from Capo --- files/lang/components_ru.ts | 4 ++-- files/lang/launcher_ru.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/files/lang/components_ru.ts b/files/lang/components_ru.ts index 1618961914..b16168effb 100644 --- a/files/lang/components_ru.ts +++ b/files/lang/components_ru.ts @@ -31,11 +31,11 @@ <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> - + <br/><b>Этот контентный файл не может быть отключен, потому что он является частью OpenMW.</b><br/> <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> - + <br/><b>Этот контентный файл не может быть отключен, потому что он включен в конфигурационном файле, не являющемся пользовательским.</b><br/> diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 4a71267eb4..843222a423 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -374,23 +374,23 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Resolved as %1 - + Путь разрешен как %1 This is the data-local directory and cannot be disabled - + Это директория data-loсal, и она не может быть отключена This directory is part of OpenMW and cannot be disabled - + Это директория является частью OpenMW и не может быть отключена This directory is enabled in an openmw.cfg other than the user one - + Это директория включена в openmw.cfg, не являющемся пользовательским This archive is enabled in an openmw.cfg other than the user one - + Этот архив включен в openmw.cfg, не являющемся пользовательским From 504a9e7d4372de1ee9000b17a91b068b91125aee Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 8 Mar 2024 17:09:49 +0100 Subject: [PATCH 215/451] Address feedback --- apps/openmw/mwgui/mapwindow.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index ae6da7766f..02a38fa640 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -96,11 +96,11 @@ namespace viewingDistanceInCells, Constants::CellGridRadius, Settings::map().mMaxLocalViewingDistance.get()); } - ESM::RefId getCellIdInWorldSpace(const MWWorld::Cell* cell, int x, int y) + ESM::RefId getCellIdInWorldSpace(const MWWorld::Cell& cell, int x, int y) { - if (cell->isExterior()) + if (cell.isExterior()) return ESM::Cell::generateIdForCell(true, {}, x, y); - return cell->getId(); + return cell.getId(); } } @@ -260,9 +260,8 @@ namespace MWGui { // normalized cell coordinates auto mapWidgetSize = getWidgetSize(); - return MyGUI::IntPoint( - std::round(nX * mapWidgetSize + (mCellDistance + (cellX - mActiveCell->getGridX())) * mapWidgetSize), - std::round(nY * mapWidgetSize + (mCellDistance - (cellY - mActiveCell->getGridY())) * mapWidgetSize)); + return MyGUI::IntPoint(std::round((nX + mCellDistance + cellX - mActiveCell->getGridX()) * mapWidgetSize), + std::round((nY + mCellDistance - cellY + mActiveCell->getGridY()) * mapWidgetSize)); } MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) const @@ -350,7 +349,7 @@ namespace MWGui for (int dY = -mCellDistance; dY <= mCellDistance; ++dY) { ESM::RefId cellRefId - = getCellIdInWorldSpace(mActiveCell, mActiveCell->getGridX() + dX, mActiveCell->getGridY() + dY); + = getCellIdInWorldSpace(*mActiveCell, mActiveCell->getGridX() + dX, mActiveCell->getGridY() + dY); CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(cellRefId); for (CustomMarkerCollection::ContainerType::const_iterator it = markers.first; it != markers.second; @@ -892,7 +891,7 @@ namespace MWGui mEditingMarker.mWorldX = worldPos.x(); mEditingMarker.mWorldY = worldPos.y(); - ESM::RefId clickedId = getCellIdInWorldSpace(mActiveCell, x, y); + ESM::RefId clickedId = getCellIdInWorldSpace(*mActiveCell, x, y); mEditingMarker.mCell = clickedId; From 84adb0a148a664bd0fbd641e021eac4dea959e0e Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 14:10:25 +0100 Subject: [PATCH 216/451] Make VFS::Path::Normalized constructor from std::string_view explicit --- apps/openmw/mwlua/vfsbindings.cpp | 3 ++- components/resource/stats.cpp | 2 +- components/vfs/pathutil.hpp | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/vfsbindings.cpp b/apps/openmw/mwlua/vfsbindings.cpp index c9b1a45fe2..34a84221f8 100644 --- a/apps/openmw/mwlua/vfsbindings.cpp +++ b/apps/openmw/mwlua/vfsbindings.cpp @@ -328,7 +328,8 @@ namespace MWLua }, [](const sol::object&) -> sol::object { return sol::nil; }); - api["fileExists"] = [vfs](std::string_view fileName) -> bool { return vfs->exists(fileName); }; + api["fileExists"] + = [vfs](std::string_view fileName) -> bool { return vfs->exists(VFS::Path::Normalized(fileName)); }; api["pathsWithPrefix"] = [vfs](std::string_view prefix) { auto iterator = vfs->getRecursiveDirectoryIterator(prefix); return sol::as_function([iterator, current = iterator.begin()]() mutable -> sol::optional { diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 6ff2112381..0542ffef28 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -29,7 +29,7 @@ namespace Resource static bool collectStatUpdate = false; static bool collectStatEngine = false; - constexpr std::string_view sFontName = "Fonts/DejaVuLGCSansMono.ttf"; + static const VFS::Path::Normalized sFontName("Fonts/DejaVuLGCSansMono.ttf"); static void setupStatCollection() { diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 5c5746cf6f..6e5c5843f3 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -130,7 +130,7 @@ namespace VFS::Path public: Normalized() = default; - Normalized(std::string_view value) + explicit Normalized(std::string_view value) : mValue(normalizeFilename(value)) { } From ffbeb5ab9853f6646443629e05f06f22a7334497 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 18:00:35 +0100 Subject: [PATCH 217/451] Build localization path using VFS::Path::Normalized --- apps/openmw_test_suite/vfs/testpathutil.cpp | 7 +++++++ components/l10n/manager.cpp | 12 +++++++----- components/vfs/pathutil.hpp | 10 ++++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/apps/openmw_test_suite/vfs/testpathutil.cpp b/apps/openmw_test_suite/vfs/testpathutil.cpp index 7b9c9abfb5..6eb84f97d5 100644 --- a/apps/openmw_test_suite/vfs/testpathutil.cpp +++ b/apps/openmw_test_suite/vfs/testpathutil.cpp @@ -72,6 +72,13 @@ namespace VFS::Path EXPECT_EQ(value.value(), "foo/bar/baz"); } + TEST(NormalizedTest, shouldSupportOperatorDivEqualWithStringView) + { + Normalized value("foo/bar"); + value /= std::string_view("BAZ"); + EXPECT_EQ(value.value(), "foo/bar/baz"); + } + TEST(NormalizedTest, changeExtensionShouldReplaceAfterLastDot) { Normalized value("foo/bar.a"); diff --git a/components/l10n/manager.cpp b/components/l10n/manager.cpp index 10cad81587..77474cd3f5 100644 --- a/components/l10n/manager.cpp +++ b/components/l10n/manager.cpp @@ -36,11 +36,13 @@ namespace l10n void Manager::readLangData(const std::string& name, MessageBundles& ctx, const icu::Locale& lang) { - std::string path = "l10n/"; - path.append(name); - path.append("/"); - path.append(lang.getName()); - path.append(".yaml"); + std::string langName(lang.getName()); + langName += ".yaml"; + + VFS::Path::Normalized path("l10n"); + path /= name; + path /= langName; + if (!mVFS->exists(path)) return; diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 6e5c5843f3..07e73acfa9 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -187,6 +187,16 @@ namespace VFS::Path return *this; } + Normalized& operator/=(std::string_view value) + { + mValue.reserve(mValue.size() + value.size() + 1); + mValue += separator; + const std::size_t offset = mValue.size(); + mValue += value; + normalizeFilenameInPlace(mValue.begin() + offset, mValue.end()); + return *this; + } + friend bool operator==(const Normalized& lhs, const Normalized& rhs) = default; friend bool operator==(const Normalized& lhs, const auto& rhs) { return lhs.mValue == rhs; } From cc35df9409cdf4060ae9261b697c7bef5b01d7b0 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 18:18:51 +0100 Subject: [PATCH 218/451] Use VFS::Path::Normalized for fx::Technique file path --- components/fx/technique.cpp | 26 ++++++++++++++++---------- components/fx/technique.hpp | 10 ++++++---- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index defb581cfc..f6bc881f78 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -37,11 +37,22 @@ namespace namespace fx { + namespace + { + VFS::Path::Normalized makeFilePath(std::string_view name) + { + std::string fileName(name); + fileName += Technique::sExt; + VFS::Path::Normalized result(Technique::sSubdir); + result /= fileName; + return result; + } + } + Technique::Technique(const VFS::Manager& vfs, Resource::ImageManager& imageManager, std::string name, int width, int height, bool ubo, bool supportsNormals) : mName(std::move(name)) - , mFileName(Files::pathToUnicodeString( - (Files::pathFromUnicodeString(Technique::sSubdir) / (mName + Technique::sExt)))) + , mFilePath(makeFilePath(mName)) , mLastModificationTime(std::filesystem::file_time_type::clock::now()) , mWidth(width) , mHeight(height) @@ -98,9 +109,9 @@ namespace fx { clear(); - if (!mVFS.exists(mFileName)) + if (!mVFS.exists(mFilePath)) { - Log(Debug::Error) << "Could not load technique, file does not exist '" << mFileName << "'"; + Log(Debug::Error) << "Could not load technique, file does not exist '" << mFilePath << "'"; mStatus = Status::File_Not_exists; return false; @@ -167,7 +178,7 @@ namespace fx mStatus = Status::Parse_Error; mLastError = "Failed parsing technique '" + getName() + "' " + e.what(); - ; + Log(Debug::Error) << mLastError; } @@ -179,11 +190,6 @@ namespace fx return mName; } - std::string Technique::getFileName() const - { - return mFileName; - } - bool Technique::setLastModificationTime(std::filesystem::file_time_type timeStamp) { const bool isDirty = timeStamp != mLastModificationTime; diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index fa66996aeb..01943a2fbe 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -13,6 +13,8 @@ #include #include +#include + #include "lexer.hpp" #include "pass.hpp" #include "types.hpp" @@ -103,8 +105,8 @@ namespace fx using UniformMap = std::vector>; using RenderTargetMap = std::unordered_map; - inline static std::string sExt = ".omwfx"; - inline static std::string sSubdir = "shaders"; + static constexpr std::string_view sExt = ".omwfx"; + static constexpr std::string_view sSubdir = "shaders"; enum class Status { @@ -128,7 +130,7 @@ namespace fx std::string getName() const; - std::string getFileName() const; + const VFS::Path::Normalized& getFileName() const { return mFilePath; } bool setLastModificationTime(std::filesystem::file_time_type timeStamp); @@ -251,7 +253,7 @@ namespace fx std::string mShared; std::string mName; - std::string mFileName; + VFS::Path::Normalized mFilePath; std::string_view mBlockName; std::string_view mAuthor; std::string_view mDescription; From 709c12053a940a2dcac17fd903b7cf3cbb6fda55 Mon Sep 17 00:00:00 2001 From: Joakim Berg Date: Sat, 9 Mar 2024 09:49:14 +0000 Subject: [PATCH 219/451] Bring sv translations up to date --- files/data/l10n/Interface/sv.yaml | 28 +++++++---- files/data/l10n/OMWControls/sv.yaml | 73 +++++++++++++++++++++++++++-- files/data/l10n/OMWEngine/sv.yaml | 42 ++++++++--------- 3 files changed, 109 insertions(+), 34 deletions(-) diff --git a/files/data/l10n/Interface/sv.yaml b/files/data/l10n/Interface/sv.yaml index aae63a1941..10a6c50b58 100644 --- a/files/data/l10n/Interface/sv.yaml +++ b/files/data/l10n/Interface/sv.yaml @@ -1,17 +1,25 @@ -Cancel: "Avbryt" -Close: "Stäng" DurationDay: "{days} d " DurationHour: "{hours} tim " DurationMinute: "{minutes} min " -DurationMonth: "{months} må " +DurationMonth: |- + {months, plural, + one{{months} må } + other{{months} må } + } DurationSecond: "{seconds} sek " -DurationYear: "{years} år " +DurationYear: |- + {years, plural, + one{{years} år } + other{{years} år } + } No: "Nej" -None: "Inget" NotAvailableShort: "N/A" -OK: "Ok" -Off: "Av" -On: "På" -Reset: "Återställ" +Reset: "Reset" Yes: "Ja" -#Copy: "Copy" +On: "På" +Off: "Av" +None: "Inget" +OK: "Ok" +Cancel: "Avbryt" +Close: "Stäng" +Copy: "Kopiera" \ No newline at end of file diff --git a/files/data/l10n/OMWControls/sv.yaml b/files/data/l10n/OMWControls/sv.yaml index 73fc5e18dc..59fecd1f35 100644 --- a/files/data/l10n/OMWControls/sv.yaml +++ b/files/data/l10n/OMWControls/sv.yaml @@ -1,7 +1,7 @@ ControlsPage: "OpenMW Kontroller" ControlsPageDescription: "Ytterligare inställningar relaterade till spelarkontroller" -MovementSettings: "Rörelse" +MovementSettings: "Rörelser" alwaysRun: "Spring alltid" alwaysRunDescription: | @@ -14,5 +14,72 @@ toggleSneakDescription: | istället för att att kräva att knappen hålls nedtryckt för att smyga. Spelare som spenderar mycket tid med att smyga lär ha lättare att kontrollera rollfiguren med denna funktion aktiverad. -# smoothControllerMovement -# smoothControllerMovementDescription +smoothControllerMovement: "Mjuka handkontrollsrörelser" +smoothControllerMovementDescription: | + Aktiverar mjuka styrspaksrörelser för handkontroller. Gör övergången från gående till springande mindre abrubt. + +TogglePOV_name: "Växla perspektiv" +TogglePOV_description: "Växlar mellan första- och tredjepersonsperspektiv. Håll in för att aktivera omloppskamera." + +Zoom3rdPerson_name: "Zooma in/ut" +Zoom3rdPerson_description: "Flyttar kameran närmare / längre bort i tredjepersonsperspektivet." + +MoveForward_name: "Förflyttning framåt" +MoveForward_description: "Kan avbrytas med Förflyttning bakåt." + +MoveBackward_name: "Förflyttning bakåt" +MoveBackward_description: "Kan avbrytas med Förflyttning framåt." + +MoveLeft_name: "Förflyttning vänster" +MoveLeft_description: "Kan avbrytas med Förflyttning höger." + +MoveRight_name: "Förflyttning höger" +MoveRight_description: "Kan avbrytas med Förflyttning vänster." + +Use_name: "Använd" +Use_description: "Attackera med ett vapen eller kasta en besvärjelse beroende på nuvarande hållning." + +Run_name: "Spring" +Run_description: "Håll in för att springa/gå, beroende på Spring alltid-inställningen." + +AlwaysRun_name: "Spring alltid" +AlwaysRun_description: "Aktiverar Spring alltid-funktionen." + +Jump_name: "Hoppa" +Jump_description: "Hoppar när du är på marken." + +AutoMove_name: "Förflytta automatiskt" +AutoMove_description: "Aktiverar konstant förflyttning framåt." + +Sneak_name: "Smyg" +Sneak_description: "Håll in för att smyga, om Växla till smyg-inställningen är av." + +ToggleSneak_name: "Växla till smygläge" +ToggleSneak_description: "Knappen för smyg växlar till smygläge med ett tryck om denna är på." + +ToggleWeapon_name: "Redo vapen" +ToggleWeapon_description: "Gå in i eller lämna vapenposition." + +ToggleSpell_name: "Redo magi" +ToggleSpell_description: "Gå in i eller lämna magiposition." + +Inventory_name: "Lager" +Inventory_description: "Öppna lagret." + +Journal_name: "Dagbok" +Journal_description: "Öppna dagboken." + +QuickKeysMenu_name: "Snabbknappsmeny" +QuickKeysMenu_description: "Öppnar snabbknappsmenyn." + +SmoothMoveForward_name: "Mjuk förflyttning framåt" +SmoothMoveForward_description: "Framåtförflyttning anpassad för mjuka övergångar från gång till spring." + +SmoothMoveBackward_name: "Mjuk förflyttning bakåt" +SmoothMoveBackward_description: "Bakåtförflyttning anpassad för mjuka övergångar från gång till spring." + +SmoothMoveLeft_name: "Mjuk förflyttning vänster" +SmoothMoveLeft_description: "Vänsterförflyttning anpassad för mjuka övergångar från gång till spring." + +SmoothMoveRight_name: "Mjuk förflyttning höger" +SmoothMoveRight_description: "Högerförflyttning anpassad för mjuka övergångar från gång till spring." diff --git a/files/data/l10n/OMWEngine/sv.yaml b/files/data/l10n/OMWEngine/sv.yaml index bbc6132f55..15a9afa495 100644 --- a/files/data/l10n/OMWEngine/sv.yaml +++ b/files/data/l10n/OMWEngine/sv.yaml @@ -22,15 +22,15 @@ LoadingInProgress: "Laddar sparat spel" LoadingRequiresNewVersionError: |- Denna sparfil skapades i en nyare version av OpenMW och stöds därför inte. Uppgradera till den senaste versionen av OpenMW för att ladda filen. -# LoadingRequiresOldVersionError: |- -# This save file was created using an older version of OpenMW in a format that is no longer supported. -# Load and save this file using {version} to upgrade it. + LoadingRequiresOldVersionError: |- + Denna sparfil skapades i en äldre version av OpenMW i ett format som inte längre stöds. + Ladda och spara denna sparfil med {version} för att uppgradera den. NewGameConfirmation: "Vill du starta ett nytt spel och förlora det pågående spelet?" QuitGameConfirmation: "Avsluta spelet?" SaveGameDenied: "Spelet kan inte sparas just nu." SavingInProgress: "Sparar..." -#ScreenshotFailed: "Failed to save screenshot" -#ScreenshotMade: "%s has been saved" +ScreenshotFailed: "Misslyckades att spara skärmdump" +ScreenshotMade: "%s har sparats" # Save game menu @@ -44,18 +44,18 @@ MissingContentFilesConfirmation: |- De valda innehållsfilerna matchar inte filerna som används av denna sparfil. Fel kan uppstå vid laddning eller under spel. Vill du fortsätta? -#MissingContentFilesList: |- -# {files, plural, -# one{\n\nFound missing file: } -# few{\n\nFound {files} missing files:\n} -# other{\n\nFound {files} missing files:\n} -# } -#MissingContentFilesListCopy: |- -# {files, plural, -# one{\n\nPress Copy to place its name to the clipboard.} -# few{\n\nPress Copy to place their names to the clipboard.} -# other{\n\nPress Copy to place their names to the clipboard.} -# } +MissingContentFilesList: |- + {files, plural, + one{\n\nHittade saknad fil: } + few{\n\nHittade {files} saknade filer:\n} + other{\n\nHittade {files} saknade filer:\n} + } +MissingContentFilesListCopy: |- + {files, plural, + one{\n\nPress Kopiera för att placera namnet i urklipp.} + few{\n\nPress Kopiera för att placera deras namn i urklipp.} + other{\n\nPress Kopiera för att placera deras namn i urklipp.} + } OverwriteGameConfirmation: "Är du säker på att du vill skriva över det här sparade spelet?" SelectCharacter: "Välj spelfigur..." @@ -109,10 +109,10 @@ LightsBoundingSphereMultiplier: "Gränssfärsmultiplikator" LightsBoundingSphereMultiplierTooltip: "Förvalt: 1.65\nMultiplikator för ljusens gränssfär.\nHögre värden ger mjukare minskning av gränssfären, men kräver högre värde i Max antal ljuskällor.\n\nPåverkar inte ljusstyrkan." LightsFadeStartMultiplier: "Blekningsstartmultiplikator" LightsFadeStartMultiplierTooltip: "Förvalt: 0.85\nFraktion av det maximala avståndet från vilket ljuskällor börjar blekna.\n\nVälj lågt värde för långsammare övergång eller högre värde för snabbare övergång." -#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n -# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n -# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n -# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." +LightsLightingMethodTooltip: "Välj intern hantering av ljuskällor.\n\n +# \"Legacy\" använder alltid max 8 ljuskällor per objekt och ger ljussättning lik ett gammaldags spel.\n\n +# \"Shader (compatibility)\" tar bort begränsningen med max 8 ljuskällor per objekt. Detta läge aktiverar också ljus på marktäckning och ett konfigurerbart ljusbleknande. Rekommenderas för äldre hårdvara tillsammans med en ljusbegränsning nära 8.\n\n +# \"Shaders\" har alla fördelar som \"Shaders (compatibility)\" har, med med ett modernt förhållningssätt som möjliggör fler maximalt antal ljuskällor med liten eller ingen prestandaförlust på modern hårdvara." LightsMaximumDistance: "Maximalt ljusavstånd" LightsMaximumDistanceTooltip: "Förvalt: 8192\nMaximala avståndet där ljuskällor syns (mätt i enheter).\n\nVärdet 0 ger oändligt avstånd." LightsMinimumInteriorBrightness: "Minsta ljusstyrka i interiörer" From 7cb316f3c90605ece6124bd86ae2bc5612ae372b Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 9 Mar 2024 16:35:24 +0100 Subject: [PATCH 220/451] Docu fix --- files/data/scripts/omw/skillhandlers.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index db726e8474..dfa9728688 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -8,7 +8,7 @@ local Skill = core.stats.Skill --- -- Table of skill use types defined by morrowind. -- Each entry corresponds to an index into the available skill gain values --- of a @{openmw.types#SkillRecord} +-- of a @{openmw.core#SkillRecord} -- @type SkillUseType -- @field #number Armor_HitByOpponent 0 -- @field #number Block_Success 0 @@ -182,7 +182,7 @@ return { --- Add new skill level up handler for this actor. -- For load order consistency, handlers should be added in the body if your script. -- If `handler(skillid, source, options)` returns false, other handlers (including the default skill level up handler) - -- will be skipped. Where skillid and source are the parameters passed to @{#skillLevelUp}, and options is + -- will be skipped. Where skillid and source are the parameters passed to @{#SkillProgression.skillLevelUp}, and options is -- a modifiable table of skill level up values, and can be modified to change the behavior of later handlers. -- These values are calculated based on vanilla mechanics. Setting any value to nil will cause that mechanic to be skipped. By default contains these values: -- @@ -203,7 +203,7 @@ return { -- For load order consistency, handlers should be added in the body of your script. -- If `handler(skillid, options)` returns false, other handlers (including the default skill progress handler) -- will be skipped. Where options is a modifiable table of skill progression values, and can be modified to change the behavior of later handlers. - -- Contains a `skillGain` value as well as a shallow copy of the options passed to @{#skillUsed}. + -- Contains a `skillGain` value as well as a shallow copy of the options passed to @{#SkillProgression.skillUsed}. -- @function [parent=#SkillProgression] addSkillUsedHandler -- @param #function handler The handler. addSkillUsedHandler = function(handler) From f3b01973ce1735a7d65fd188300458e33df73f98 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 9 Mar 2024 17:12:47 +0000 Subject: [PATCH 221/451] c h a n g e l o g --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea44cab4b1..d282b603f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW Bug #6758: Main menu background video can be stopped by opening the options menu Bug #6807: Ultimate Galleon is not working properly + Bug #6846: Launcher only works with default config paths 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 From 72cf015401f3b455b9860a617b1bf5d9312c75f6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 9 Mar 2024 20:18:41 +0000 Subject: [PATCH 222/451] Make ccache viable for Windows Release builds --- CI/before_script.msvc.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index e11ceb499d..fdbd27fb9c 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -528,8 +528,10 @@ if ! [ -z $UNITY_BUILD ]; then add_cmake_opts "-DOPENMW_UNITY_BUILD=True" fi -if ! [ -z $USE_CCACHE ]; then - add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache" +if [ -n "$USE_CCACHE" ] && ([ -n "$NMAKE" ] || [ -n "$NINJA" ]); then + add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DPRECOMPILE_HEADERS_WITH_MSVC=OFF" +elif [ -n "$USE_CCACHE" ]; then + echo "Ignoring -C (CCache) as it is incompatible with Visual Studio CMake generators" fi # turn on LTO by default From 57d7f5977c8589572ac46f6c0ef7451b41bccdaf Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 9 Mar 2024 21:56:46 +0100 Subject: [PATCH 223/451] Bump interface version --- files/data/scripts/omw/skillhandlers.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/skillhandlers.lua b/files/data/scripts/omw/skillhandlers.lua index dfa9728688..e3ca24f9d0 100644 --- a/files/data/scripts/omw/skillhandlers.lua +++ b/files/data/scripts/omw/skillhandlers.lua @@ -177,7 +177,7 @@ return { interface = { --- Interface version -- @field [parent=#SkillProgression] #number version - version = 0, + version = 1, --- Add new skill level up handler for this actor. -- For load order consistency, handlers should be added in the body if your script. From 0f60052bb87c36c5a767f23246b920d1a2bebe86 Mon Sep 17 00:00:00 2001 From: uramer Date: Sat, 9 Mar 2024 22:27:10 +0100 Subject: [PATCH 224/451] Set Element state in Element::create --- components/lua_ui/element.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 6e7fe9ee16..e509847a4c 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -278,6 +278,7 @@ namespace LuaUi mRoot = createWidget(layout(), true, depth); mLayer = setLayer(mRoot, layout()); updateRootCoord(mRoot); + mState = Created; } } @@ -304,8 +305,8 @@ namespace LuaUi } mLayer = setLayer(mRoot, layout()); updateRootCoord(mRoot); + mState = Created; } - mState = Created; } void Element::destroy() From 7b89ca6bb2eadec61e1ade0087a5ac58d8077159 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 10 Mar 2024 01:31:55 +0000 Subject: [PATCH 225/451] Make CCache work for MSVC builds with debug symbols --- CMakeLists.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 50af98393e..741295acf0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,6 +191,22 @@ if (MSVC) add_compile_options(/bigobj) add_compile_options(/Zc:__cplusplus) + + if (CMAKE_CXX_COMPILER_LAUNCHER OR CMAKE_C_COMPILER_LAUNCHER) + if (CMAKE_GENERATOR MATCHES "Visual Studio") + message(STATUS "A compiler launcher was specified, but will be unused by the current generator (${CMAKE_GENERATOR})") + else() + foreach (config_lower ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER "${config_lower}" config) + if (CMAKE_C_COMPILER_LAUNCHER STREQUAL "ccache") + string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_${config} "${CMAKE_C_FLAGS_${config}}") + endif() + if (CMAKE_CXX_COMPILER_LAUNCHER STREQUAL "ccache") + string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_${config} "${CMAKE_CXX_FLAGS_${config}}") + endif() + endforeach() + endif() + endif() endif() # Set up common paths From 6cf0b9990d6390794d78c002e344bbdc029ed1e1 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 10 Mar 2024 01:32:38 +0000 Subject: [PATCH 226/451] Don't bother setting up CCache for MSBuild builds It can't work as it ignores compiler launchers --- .gitlab-ci.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2bb4193a79..06c7e63cb0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -576,7 +576,7 @@ macOS14_Xcode15_arm64: - cd MSVC2019_64_Ninja - .\ActivateMSVC.ps1 - cmake --build . --config $config - - ccache --show-stats + - ccache --show-stats -v - cd $config - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - $artifactDirectory = "$(Make-SafeFileName("${CI_PROJECT_NAMESPACE}"))/$(Make-SafeFileName("${CI_COMMIT_REF_NAME}"))/$(Make-SafeFileName("${CI_COMMIT_SHORT_SHA}-${CI_JOB_ID}"))/" @@ -666,7 +666,6 @@ macOS14_Xcode15_arm64: - choco source disable -n=chocolatey - choco install git --force --params "/GitAndUnixToolsOnPath" -y - choco install 7zip -y - - choco install ccache -y - choco install vswhere -y - choco install python -y - choco install awscli -y @@ -689,15 +688,11 @@ macOS14_Xcode15_arm64: - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - - $env:CCACHE_BASEDIR = Get-Location - - $env:CCACHE_DIR = "$(Get-Location)\ccache" - - New-Item -Type Directory -Force -Path $env:CCACHE_DIR - New-Item -Type File -Force -Path MSVC2019_64\.cmake\api\v1\query\codemodel-v2 - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C $multiview -E - cd MSVC2019_64 - Get-Volume - cmake --build . --config $config - - ccache --show-stats - cd $config - echo "CI_COMMIT_REF_NAME ${CI_COMMIT_REF_NAME}`nCI_JOB_ID ${CI_JOB_ID}`nCI_COMMIT_SHA ${CI_COMMIT_SHA}" | Out-File -Encoding UTF8 CI-ID.txt - $artifactDirectory = "$(Make-SafeFileName("${CI_PROJECT_NAMESPACE}"))/$(Make-SafeFileName("${CI_COMMIT_REF_NAME}"))/$(Make-SafeFileName("${CI_COMMIT_SHORT_SHA}-${CI_JOB_ID}"))/" @@ -729,7 +724,6 @@ macOS14_Xcode15_arm64: cache: key: msbuild-v8 paths: - - ccache - deps - MSVC2019_64/deps/Qt artifacts: From 30f314025afdcbfbfeb0de3a4650d518c369e407 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 8 Mar 2024 22:19:31 +0300 Subject: [PATCH 227/451] Log whether shaders or FFP are used for rendering --- apps/openmw/mwrender/renderingmanager.cpp | 55 ++++++++++++++++++++--- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 2bfbf179ea..004b041336 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -329,13 +329,56 @@ namespace MWRender const SceneUtil::LightingMethod lightingMethod = Settings::shaders().mLightingMethod; resourceSystem->getSceneManager()->setParticleSystemMask(MWRender::Mask_ParticleSystem); - // Shadows and radial fog have problems with fixed-function mode. - bool forceShaders = Settings::fog().mRadialFog || Settings::fog().mExponentialFog - || Settings::shaders().mSoftParticles || Settings::shaders().mForceShaders - || Settings::shadows().mEnableShadows || lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ - || mSkyBlending || Stereo::getMultiview(); - resourceSystem->getSceneManager()->setForceShaders(forceShaders); + // Figure out which pipeline must be used by default and inform the user + bool forceShaders = Settings::shaders().mForceShaders; + { + std::vector requesters; + if (!forceShaders) + { + if (Settings::fog().mRadialFog) + requesters.push_back("radial fog"); + if (Settings::fog().mExponentialFog) + requesters.push_back("exponential fog"); + if (mSkyBlending) + requesters.push_back("sky blending"); + if (Settings::shaders().mSoftParticles) + requesters.push_back("soft particles"); + if (Settings::shadows().mEnableShadows) + requesters.push_back("shadows"); + if (lightingMethod != SceneUtil::LightingMethod::FFP) + requesters.push_back("lighting method"); + if (reverseZ) + requesters.push_back("reverse-Z depth buffer"); + if (Stereo::getMultiview()) + requesters.push_back("stereo multiview"); + + if (!requesters.empty()) + forceShaders = true; + } + + if (forceShaders) + { + std::string message = "Using rendering with shaders by default"; + if (requesters.empty()) + { + message += " (forced)"; + } + else + { + message += ", requested by:"; + for (size_t i = 0; i < requesters.size(); i++) + message += "\n - " + requesters[i]; + } + Log(Debug::Info) << message; + } + else + { + Log(Debug::Info) << "Using fixed-function rendering by default"; + } + } + + resourceSystem->getSceneManager()->setForceShaders(forceShaders); // FIXME: calling dummy method because terrain needs to know whether lighting is clamped resourceSystem->getSceneManager()->setClampLighting(Settings::shaders().mClampLighting); resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::shaders().mAutoUseObjectNormalMaps); From f7e5ef74c6b671529c8cca50df4b0540d2ff1b97 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 10 Mar 2024 14:53:55 +0400 Subject: [PATCH 228/451] Partially revert 5dcac4c48f54 --- CHANGELOG.md | 1 - apps/openmw/mwgui/windowmanagerimp.cpp | 3 --- 2 files changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddbab574d5..953801b345 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -157,7 +157,6 @@ Bug #7840: First run of the launcher doesn't save viewing distance as the default value Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs Bug #7859: AutoCalc flag is not used to calculate potion value - Bug #7866: Alt-tabbing is considered as a resolution change Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index b8c92d761e..29b9cb0e84 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1166,9 +1166,6 @@ namespace MWGui void WindowManager::windowResized(int x, int y) { - if (x == Settings::video().mResolutionX && y == Settings::video().mResolutionY) - return; - Settings::video().mResolutionX.set(x); Settings::video().mResolutionY.set(y); From af8662daeebabeab20cf8403867259495de6c3bd Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 10 Mar 2024 14:05:37 +0100 Subject: [PATCH 229/451] Detach Lua Elements properly from their parent --- components/lua_ui/element.cpp | 8 ++++---- components/lua_ui/widget.cpp | 6 ++++++ components/lua_ui/widget.hpp | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index e509847a4c..b491acb7b3 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -54,7 +54,7 @@ namespace LuaUi if (!ext->isRoot()) destroyWidget(ext); else - ext->widget()->detachFromWidget(); + ext->detachFromParent(); } void detachElements(WidgetExtension* ext) @@ -62,14 +62,14 @@ namespace LuaUi for (auto* child : ext->children()) { if (child->isRoot()) - child->widget()->detachFromWidget(); + child->detachFromParent(); else detachElements(child); } for (auto* child : ext->templateChildren()) { if (child->isRoot()) - child->widget()->detachFromWidget(); + child->detachFromParent(); else detachElements(child); } @@ -272,9 +272,9 @@ namespace LuaUi void Element::create(uint64_t depth) { - assert(!mRoot); if (mState == New) { + assert(!mRoot); mRoot = createWidget(layout(), true, depth); mLayer = setLayer(mRoot, layout()); updateRootCoord(mRoot); diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 9550c9de73..be0ea70387 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -112,6 +112,12 @@ namespace LuaUi ext->widget()->attachToWidget(widget()); } + void WidgetExtension::detachFromParent() + { + mParent = nullptr; + widget()->detachFromWidget(); + } + WidgetExtension* WidgetExtension::findDeep(std::string_view flagName) { for (WidgetExtension* w : mChildren) diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 591c885ce9..05359705a1 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -35,6 +35,7 @@ namespace LuaUi bool isRoot() const { return mElementRoot; } WidgetExtension* getParent() const { return mParent; } + void detachFromParent(); void reset(); From eba4ae94b0350ed2837962d22caa4ff1e5c0e52b Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 10 Mar 2024 14:06:21 +0100 Subject: [PATCH 230/451] Fix re-rendering of settings on value changes --- files/data/scripts/omw/settings/menu.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/files/data/scripts/omw/settings/menu.lua b/files/data/scripts/omw/settings/menu.lua index 7d425f684b..4e6971a516 100644 --- a/files/data/scripts/omw/settings/menu.lua +++ b/files/data/scripts/omw/settings/menu.lua @@ -378,7 +378,7 @@ local function onSettingChanged(global) local value = common.getSection(global, group.key):get(settingKey) local settingsContent = groupElement.layout.content.settings.content auxUi.deepDestroy(settingsContent[settingKey]) -- support setting renderers which return UI elements - settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value) + settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) groupElement:update() end) end @@ -408,7 +408,7 @@ local function onGroupRegistered(global, key) local element = groupElements[group.page][group.key] local settingsContent = element.layout.content.settings.content - settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value) + settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global) element:update() end)) end From 0730dc2ebb9751bfbbef4dd5b392ac0c71b45f2d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 10 Mar 2024 18:04:38 +0000 Subject: [PATCH 231/451] Get OSG to tell us the plugin filenames it's going to use That way, we don't have issues like the checker getting false positives when the right plugins are present for the wrong OSG version or build config, or false negatives when we've generated the wrong filenames. --- components/CMakeLists.txt | 15 ++++----------- components/misc/osgpluginchecker.cpp.in | 9 ++++++--- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 1c553e855e..7e422506ad 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -46,17 +46,10 @@ list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") # OSG plugin checker # Helpfully, OSG doesn't export this to its CMake config as it doesn't have one -set(OSG_PLUGIN_PREFIX "") -if (CYGWIN) - SET(OSG_PLUGIN_PREFIX "cygwin_") -elseif(MINGW) - SET(OSG_PLUGIN_PREFIX "mingw_") -endif() -list(TRANSFORM USED_OSG_PLUGINS PREPEND "${OSG_PLUGIN_PREFIX}" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES) -list(TRANSFORM USED_OSG_PLUGIN_FILENAMES APPEND "${CMAKE_SHARED_MODULE_SUFFIX}") -list(TRANSFORM USED_OSG_PLUGIN_FILENAMES PREPEND "\"" OUTPUT_VARIABLE USED_OSG_PLUGIN_FILENAMES_FORMATTED) -list(TRANSFORM USED_OSG_PLUGIN_FILENAMES_FORMATTED APPEND "\"") -list(JOIN USED_OSG_PLUGIN_FILENAMES_FORMATTED ", " USED_OSG_PLUGIN_FILENAMES_FORMATTED) +list(TRANSFORM USED_OSG_PLUGINS REPLACE "^osgdb_" "" OUTPUT_VARIABLE USED_OSG_PLUGIN_NAMES) +list(TRANSFORM USED_OSG_PLUGIN_NAMES PREPEND "\"" OUTPUT_VARIABLE USED_OSG_PLUGIN_NAMES_FORMATTED) +list(TRANSFORM USED_OSG_PLUGIN_NAMES_FORMATTED APPEND "\"") +list(JOIN USED_OSG_PLUGIN_NAMES_FORMATTED ", " USED_OSG_PLUGIN_NAMES_FORMATTED) set(OSG_PLUGIN_CHECKER_CPP_FILE "components/misc/osgpluginchecker.cpp") configure_file("${OpenMW_SOURCE_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}.in" "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE}") diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index f519447752..81ae73f9e3 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -30,7 +30,7 @@ namespace Misc namespace { - constexpr auto USED_OSG_PLUGIN_FILENAMES = std::to_array({${USED_OSG_PLUGIN_FILENAMES_FORMATTED}}); + constexpr auto USED_OSG_PLUGIN_NAMES = std::to_array({${USED_OSG_PLUGIN_NAMES_FORMATTED}}); } bool checkRequiredOSGPluginsArePresent() @@ -62,7 +62,7 @@ namespace Misc auto availableOSGPlugins = osgDB::listAllAvailablePlugins(); bool haveAllPlugins = true; - for (std::string_view plugin : USED_OSG_PLUGIN_FILENAMES) + for (std::string_view plugin : USED_OSG_PLUGIN_NAMES) { if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), [&](std::string_view availablePlugin) { @@ -71,7 +71,10 @@ namespace Misc #else std::filesystem::path pluginPath {availablePlugin}; #endif - return pluginPath.filename() == plugin; + return pluginPath.filename() + == std::filesystem::path( + osgDB::Registry::instance()->createLibraryNameForExtension(std::string{ plugin })) + .filename(); }) == availableOSGPlugins.end()) { From 7ec723e9b9a91f2afb6496a2829d85b87fec5f8b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 10 Mar 2024 23:26:45 +0000 Subject: [PATCH 232/451] More sensible conditions --- CI/before_script.msvc.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index fdbd27fb9c..269cc44707 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -528,10 +528,12 @@ if ! [ -z $UNITY_BUILD ]; then add_cmake_opts "-DOPENMW_UNITY_BUILD=True" fi -if [ -n "$USE_CCACHE" ] && ([ -n "$NMAKE" ] || [ -n "$NINJA" ]); then - add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DPRECOMPILE_HEADERS_WITH_MSVC=OFF" -elif [ -n "$USE_CCACHE" ]; then - echo "Ignoring -C (CCache) as it is incompatible with Visual Studio CMake generators" +if [ -n "$USE_CCACHE" ]; then + if [ -n "$NMAKE" ] || [ -n "$NINJA" ]; then + add_cmake_opts "-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DPRECOMPILE_HEADERS_WITH_MSVC=OFF" + else + echo "Ignoring -C (CCache) as it is incompatible with Visual Studio CMake generators" + fi fi # turn on LTO by default From 6232b4f9e86556b5fcb7516aa672fb051a602639 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 21 Feb 2024 22:54:50 +0300 Subject: [PATCH 233/451] Reimplement the Settings window as a normal window (#7845, #7870) --- apps/openmw/mwbase/windowmanager.hpp | 3 ++- apps/openmw/mwgui/mainmenu.cpp | 8 +++++- apps/openmw/mwgui/settingswindow.cpp | 4 +-- apps/openmw/mwgui/settingswindow.hpp | 2 +- apps/openmw/mwgui/windowmanagerimp.cpp | 27 ++++++++++++++++--- apps/openmw/mwgui/windowmanagerimp.hpp | 3 ++- apps/openmw/mwinput/mousemanager.cpp | 4 +-- files/data/mygui/openmw_layers.xml | 1 + .../data/mygui/openmw_settings_window.layout | 2 +- 9 files changed, 39 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index c252e0c490..c511ac313d 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -136,6 +136,7 @@ namespace MWBase virtual bool isConsoleMode() const = 0; virtual bool isPostProcessorHudVisible() const = 0; + virtual bool isSettingsWindowVisible() const = 0; virtual bool isInteractiveMessageBoxActive() const = 0; virtual void toggleVisible(MWGui::GuiWindow wnd) = 0; @@ -157,7 +158,6 @@ namespace MWBase virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0; virtual MWGui::PostProcessorHud* getPostProcessorHud() = 0; - virtual MWGui::SettingsWindow* getSettingsWindow() = 0; /// Make the player use an item, while updating GUI state accordingly virtual void useItem(const MWWorld::Ptr& item, bool force = false) = 0; @@ -342,6 +342,7 @@ namespace MWBase virtual void toggleConsole() = 0; virtual void toggleDebugWindow() = 0; virtual void togglePostProcessorHud() = 0; + virtual void toggleSettingsWindow() = 0; /// Cycle to next or previous spell virtual void cycleSpell(bool next) = 0; diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index d0c55f432e..be3700342a 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -99,7 +99,7 @@ namespace MWGui } else if (name == "options") { - winMgr->getSettingsWindow()->setVisible(true); + winMgr->toggleSettingsWindow(); } else if (name == "credits") winMgr->playVideo("mw_credits.bik", true); @@ -212,6 +212,12 @@ namespace MWGui bool MainMenu::exit() { + if (MWBase::Environment::get().getWindowManager()->isSettingsWindowVisible()) + { + MWBase::Environment::get().getWindowManager()->toggleSettingsWindow(); + return false; + } + return MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_Running; } diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 6c6a34595e..b569132141 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -241,7 +241,7 @@ namespace MWGui } SettingsWindow::SettingsWindow() - : WindowModal("openmw_settings_window.layout") + : WindowBase("openmw_settings_window.layout") , mKeyboardMode(true) , mCurrentPage(-1) { @@ -1042,8 +1042,6 @@ namespace MWGui void SettingsWindow::onOpen() { - WindowModal::onOpen(); - highlightCurrentResolution(); updateControlsBox(); updateLightSettings(); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 47951ef121..1f96f7de54 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -7,7 +7,7 @@ namespace MWGui { - class SettingsWindow : public WindowModal + class SettingsWindow : public WindowBase { public: SettingsWindow(); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index b8c92d761e..ab5e23eeac 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -914,6 +914,9 @@ namespace MWGui if (isConsoleMode()) mConsole->onFrame(frameDuration); + if (isSettingsWindowVisible()) + mSettingsWindow->onFrame(frameDuration); + if (!gameRunning) return; @@ -1473,10 +1476,6 @@ namespace MWGui { return mPostProcessorHud; } - MWGui::SettingsWindow* WindowManager::getSettingsWindow() - { - return mSettingsWindow; - } void WindowManager::useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions) { @@ -1552,6 +1551,11 @@ namespace MWGui return mPostProcessorHud && mPostProcessorHud->isVisible(); } + bool WindowManager::isSettingsWindowVisible() const + { + return mSettingsWindow && mSettingsWindow->isVisible(); + } + bool WindowManager::isInteractiveMessageBoxActive() const { return mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox(); @@ -2133,6 +2137,21 @@ namespace MWGui updateVisible(); } + void WindowManager::toggleSettingsWindow() + { + bool visible = mSettingsWindow->isVisible(); + + if (!visible && !mGuiModes.empty()) + mKeyboardNavigation->saveFocus(mGuiModes.back()); + + mSettingsWindow->setVisible(!visible); + + if (visible && !mGuiModes.empty()) + mKeyboardNavigation->restoreFocus(mGuiModes.back()); + + updateVisible(); + } + void WindowManager::cycleSpell(bool next) { if (!isGuiMode()) diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 3445ebdb9a..ddc9e1c5e0 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -162,6 +162,7 @@ namespace MWGui bool isConsoleMode() const override; bool isPostProcessorHudVisible() const override; + bool isSettingsWindowVisible() const override; bool isInteractiveMessageBoxActive() const override; void toggleVisible(GuiWindow wnd) override; @@ -183,7 +184,6 @@ namespace MWGui MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::TradeWindow* getTradeWindow() override; MWGui::PostProcessorHud* getPostProcessorHud() override; - MWGui::SettingsWindow* getSettingsWindow() override; /// Make the player use an item, while updating GUI state accordingly void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions = false) override; @@ -364,6 +364,7 @@ namespace MWGui void toggleConsole() override; void toggleDebugWindow() override; void togglePostProcessorHud() override; + void toggleSettingsWindow() override; /// Cycle to next or previous spell void cycleSpell(bool next) override; diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index f18ec2ac87..9a8cada25b 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -166,9 +166,7 @@ namespace MWInput // Don't trigger any mouse bindings while in settings menu, otherwise rebinding controls becomes impossible // Also do not trigger bindings when input controls are disabled, e.g. during save loading - const MWGui::SettingsWindow* settingsWindow - = MWBase::Environment::get().getWindowManager()->getSettingsWindow(); - if ((!settingsWindow || !settingsWindow->isVisible()) && !input->controlsDisabled()) + if (!MWBase::Environment::get().getWindowManager()->isSettingsWindowVisible() && !input->controlsDisabled()) { mBindingsManager->mousePressed(arg, id); } diff --git a/files/data/mygui/openmw_layers.xml b/files/data/mygui/openmw_layers.xml index 045fb1cdc2..459db3fcb9 100644 --- a/files/data/mygui/openmw_layers.xml +++ b/files/data/mygui/openmw_layers.xml @@ -13,6 +13,7 @@ + diff --git a/files/data/mygui/openmw_settings_window.layout b/files/data/mygui/openmw_settings_window.layout index 27298b9756..9e2f707ef5 100644 --- a/files/data/mygui/openmw_settings_window.layout +++ b/files/data/mygui/openmw_settings_window.layout @@ -1,6 +1,6 @@  - + From cd3c3ebadb8d288741d71d58b13fdfb269e279dd Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 18:42:21 +0100 Subject: [PATCH 234/451] Use VFS::Path::Normalized for ResourceManager cache key --- components/resource/resourcemanager.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index b2427c308a..e2626665c8 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -3,6 +3,8 @@ #include +#include + #include "objectcache.hpp" namespace VFS @@ -70,11 +72,11 @@ namespace Resource double mExpiryDelay; }; - class ResourceManager : public GenericResourceManager + class ResourceManager : public GenericResourceManager { public: explicit ResourceManager(const VFS::Manager* vfs, double expiryDelay) - : GenericResourceManager(vfs, expiryDelay) + : GenericResourceManager(vfs, expiryDelay) { } }; From 79b73e45a12b322f611c7004ef8eb4591c12e904 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 18:57:43 +0100 Subject: [PATCH 235/451] Replace std::filesystem::path by std::string and std::string_view in nif code It's used only for error reporting. --- apps/niftest/niftest.cpp | 4 ++-- components/nif/exception.hpp | 13 ++++++++----- components/nif/niffile.hpp | 12 ++++++------ components/nifbullet/bulletnifloader.cpp | 2 +- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index fe60238cd5..b37d85d739 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -65,10 +65,10 @@ void readNIF( std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; std::cout << std::endl; } - std::filesystem::path fullPath = !source.empty() ? source / path : path; + const std::filesystem::path fullPath = !source.empty() ? source / path : path; try { - Nif::NIFFile file(fullPath); + Nif::NIFFile file(Files::pathToUnicodeString(fullPath)); Nif::Reader reader(file, nullptr); if (vfs != nullptr) reader.parse(vfs->get(pathStr)); diff --git a/components/nif/exception.hpp b/components/nif/exception.hpp index 15f0e76d70..b123d6dc4f 100644 --- a/components/nif/exception.hpp +++ b/components/nif/exception.hpp @@ -1,18 +1,21 @@ #ifndef OPENMW_COMPONENTS_NIF_EXCEPTION_HPP #define OPENMW_COMPONENTS_NIF_EXCEPTION_HPP -#include #include #include -#include - namespace Nif { struct Exception : std::runtime_error { - explicit Exception(const std::string& message, const std::filesystem::path& path) - : std::runtime_error("NIFFile Error: " + message + " when reading " + Files::pathToUnicodeString(path)) + explicit Exception(std::string_view message, std::string_view path) + : std::runtime_error([&] { + std::string result = "NIFFile Error: "; + result += message; + result += " when reading "; + result += path; + return result; + }()) { } }; diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 993e9b7eea..6bff30a225 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -4,7 +4,7 @@ #define OPENMW_COMPONENTS_NIF_NIFFILE_HPP #include -#include +#include #include #include @@ -45,7 +45,7 @@ namespace Nif std::uint32_t mBethVersion = 0; /// File name, used for error messages and opening the file - std::filesystem::path mPath; + std::string mPath; std::string mHash; /// Record list @@ -56,7 +56,7 @@ namespace Nif bool mUseSkinning = false; - explicit NIFFile(const std::filesystem::path& path) + explicit NIFFile(std::string_view path) : mPath(path) { } @@ -77,7 +77,7 @@ namespace Nif std::size_t numRoots() const { return mFile->mRoots.size(); } /// Get the name of the file - const std::filesystem::path& getFilename() const { return mFile->mPath; } + const std::string& getFilename() const { return mFile->mPath; } const std::string& getHash() const { return mFile->mHash; } @@ -104,7 +104,7 @@ namespace Nif std::uint32_t& mBethVersion; /// File name, used for error messages and opening the file - std::filesystem::path& mFilename; + std::string_view mFilename; std::string& mHash; /// Record list @@ -144,7 +144,7 @@ namespace Nif void setUseSkinning(bool skinning); /// Get the name of the file - std::filesystem::path getFilename() const { return mFilename; } + std::string_view getFilename() const { return mFilename; } /// Get the version of the NIF format used std::uint32_t getVersion() const { return mVersion; } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 0737d0a165..a57e8b3c06 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -50,7 +50,7 @@ namespace NifBullet if (node) roots.emplace_back(node); } - mShape->mFileName = Files::pathToUnicodeString(nif.getFilename()); + mShape->mFileName = nif.getFilename(); if (roots.empty()) { warn("Found no root nodes in NIF file " + mShape->mFileName); From a98ce7f76a6a0d78857e7a5476bc48f0c8f969fa Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 19:02:41 +0100 Subject: [PATCH 236/451] Replace std::filesystem::path by std::string_view in Files::getHash argument --- apps/openmw_test_suite/files/hash.cpp | 9 ++++++--- components/files/hash.cpp | 10 ++++++---- components/files/hash.hpp | 4 ++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apps/openmw_test_suite/files/hash.cpp b/apps/openmw_test_suite/files/hash.cpp index 6ad19713dc..32c8380422 100644 --- a/apps/openmw_test_suite/files/hash.cpp +++ b/apps/openmw_test_suite/files/hash.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "../testing_util.hpp" namespace @@ -35,7 +37,8 @@ namespace std::fill_n(std::back_inserter(content), 1, 'a'); std::istringstream stream(content); stream.exceptions(std::ios::failbit | std::ios::badbit); - EXPECT_THAT(getHash(fileName, stream), ElementsAre(9607679276477937801ull, 16624257681780017498ull)); + EXPECT_THAT(getHash(Files::pathToUnicodeString(fileName), stream), + ElementsAre(9607679276477937801ull, 16624257681780017498ull)); } TEST_P(FilesGetHash, shouldReturnHashForStringStream) @@ -44,7 +47,7 @@ namespace std::string content; std::fill_n(std::back_inserter(content), GetParam().mSize, 'a'); std::istringstream stream(content); - EXPECT_EQ(getHash(fileName, stream), GetParam().mHash); + EXPECT_EQ(getHash(Files::pathToUnicodeString(fileName), stream), GetParam().mHash); } TEST_P(FilesGetHash, shouldReturnHashForConstrainedFileStream) @@ -57,7 +60,7 @@ namespace std::fstream(file, std::ios_base::out | std::ios_base::binary) .write(content.data(), static_cast(content.size())); const auto stream = Files::openConstrainedFileStream(file, 0, content.size()); - EXPECT_EQ(getHash(file, *stream), GetParam().mHash); + EXPECT_EQ(getHash(Files::pathToUnicodeString(file), *stream), GetParam().mHash); } INSTANTIATE_TEST_SUITE_P(Params, FilesGetHash, diff --git a/components/files/hash.cpp b/components/files/hash.cpp index afb59b2e9e..1f1839ed0c 100644 --- a/components/files/hash.cpp +++ b/components/files/hash.cpp @@ -1,5 +1,4 @@ #include "hash.hpp" -#include "conversion.hpp" #include @@ -10,7 +9,7 @@ namespace Files { - std::array getHash(const std::filesystem::path& fileName, std::istream& stream) + std::array getHash(std::string_view fileName, std::istream& stream) { std::array hash{ 0, 0 }; try @@ -35,8 +34,11 @@ namespace Files } catch (const std::exception& e) { - throw std::runtime_error( - "Error while reading \"" + Files::pathToUnicodeString(fileName) + "\" to get hash: " + e.what()); + std::string message = "Error while reading \""; + message += fileName; + message += "\" to get hash: "; + message += e.what(); + throw std::runtime_error(message); } return hash; } diff --git a/components/files/hash.hpp b/components/files/hash.hpp index 0e6ce29ab5..48c373b971 100644 --- a/components/files/hash.hpp +++ b/components/files/hash.hpp @@ -3,12 +3,12 @@ #include #include -#include #include +#include namespace Files { - std::array getHash(const std::filesystem::path& fileName, std::istream& stream); + std::array getHash(std::string_view fileName, std::istream& stream); } #endif From 3ea3eeb6136fc7a98343cbbd87cb8de5da5bc9e9 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Mar 2024 01:20:56 +0100 Subject: [PATCH 237/451] Use string_view for canOptimize --- components/resource/scenemanager.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 787f2e8441..26d719a106 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -778,12 +778,12 @@ namespace Resource } }; - bool canOptimize(const std::string& filename) + static bool canOptimize(std::string_view filename) { - size_t slashpos = filename.find_last_of("\\/"); - if (slashpos != std::string::npos && slashpos + 1 < filename.size()) + const std::string_view::size_type slashpos = filename.find_last_of('/'); + if (slashpos != std::string_view::npos && slashpos + 1 < filename.size()) { - std::string basename = filename.substr(slashpos + 1); + const std::string_view basename = filename.substr(slashpos + 1); // xmesh.nif can not be optimized because there are keyframes added in post if (!basename.empty() && basename[0] == 'x') return false; @@ -796,7 +796,7 @@ namespace Resource // For spell VFX, DummyXX nodes must remain intact. Not adding those to reservedNames to avoid being overly // cautious - instead, decide on filename - if (filename.find("vfx_pattern") != std::string::npos) + if (filename.find("vfx_pattern") != std::string_view::npos) return false; return true; } From 859d76592108b083fa3284582e7ec7958bb561b9 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 9 Mar 2024 01:45:21 +0100 Subject: [PATCH 238/451] Use normalized path for NifFileManager::get --- components/resource/bulletshapemanager.cpp | 2 +- components/resource/niffilemanager.cpp | 4 ++-- components/resource/niffilemanager.hpp | 2 +- components/resource/scenemanager.cpp | 19 ++++++++++--------- components/vfs/manager.cpp | 5 +++++ components/vfs/manager.hpp | 2 ++ 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index f817d6b89a..b37e81111d 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -110,7 +110,7 @@ namespace Resource osg::ref_ptr BulletShapeManager::getShape(const std::string& name) { - const std::string normalized = VFS::Path::normalizeFilename(name); + const VFS::Path::Normalized normalized(name); osg::ref_ptr shape; osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index 352d367f9b..0cc48d4247 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -41,14 +41,14 @@ namespace Resource NifFileManager::~NifFileManager() = default; - Nif::NIFFilePtr NifFileManager::get(const std::string& name) + Nif::NIFFilePtr NifFileManager::get(VFS::Path::NormalizedView name) { osg::ref_ptr obj = mCache->getRefFromObjectCache(name); if (obj) return static_cast(obj.get())->mNifFile; else { - auto file = std::make_shared(name); + auto file = std::make_shared(name.value()); Nif::Reader reader(*file, mEncoder); reader.parse(mVFS->get(name)); obj = new NifFileHolder(file); diff --git a/components/resource/niffilemanager.hpp b/components/resource/niffilemanager.hpp index dab4b70748..a5395fef7e 100644 --- a/components/resource/niffilemanager.hpp +++ b/components/resource/niffilemanager.hpp @@ -26,7 +26,7 @@ namespace Resource /// Retrieve a NIF file from the cache, or load it from the VFS if not cached yet. /// @note For performance reasons the NifFileManager does not handle case folding, needs /// to be done in advance by other managers accessing the NifFileManager. - Nif::NIFFilePtr get(const std::string& name); + Nif::NIFFilePtr get(VFS::Path::NormalizedView name); void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; }; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 26d719a106..9ed72d5f05 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -545,9 +545,9 @@ namespace Resource namespace { osg::ref_ptr loadNonNif( - const std::string& normalizedFilename, std::istream& model, Resource::ImageManager* imageManager) + VFS::Path::NormalizedView normalizedFilename, std::istream& model, Resource::ImageManager* imageManager) { - auto ext = Misc::getFileExtension(normalizedFilename); + const std::string_view ext = Misc::getFileExtension(normalizedFilename.value()); osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(std::string(ext)); if (!reader) { @@ -566,7 +566,7 @@ namespace Resource if (ext == "dae") options->setOptionString("daeUseSequencedTextureUnits"); - const std::array fileHash = Files::getHash(normalizedFilename, model); + const std::array fileHash = Files::getHash(normalizedFilename.value(), model); osgDB::ReaderWriter::ReadResult result = reader->readNode(model, options); if (!result.success()) @@ -721,10 +721,10 @@ namespace Resource } } - osg::ref_ptr load(const std::string& normalizedFilename, const VFS::Manager* vfs, + osg::ref_ptr load(VFS::Path::NormalizedView normalizedFilename, const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) { - auto ext = Misc::getFileExtension(normalizedFilename); + const std::string_view ext = Misc::getFileExtension(normalizedFilename.value()); if (ext == "nif") return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager); else if (ext == "spt") @@ -843,11 +843,12 @@ namespace Resource { try { + VFS::Path::Normalized path("meshes/marker_error.****"); for (const auto meshType : { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae" }) { - const std::string normalized = "meshes/marker_error." + std::string(meshType); - if (mVFS->exists(normalized)) - return load(normalized, mVFS, mImageManager, mNifFileManager); + path.changeExtension(meshType); + if (mVFS->exists(path)) + return load(path, mVFS, mImageManager, mNifFileManager); } } catch (const std::exception& e) @@ -869,7 +870,7 @@ namespace Resource osg::ref_ptr SceneManager::getTemplate(std::string_view name, bool compile) { - std::string normalized = VFS::Path::normalizeFilename(name); + const VFS::Path::Normalized normalized(name); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index ef5dd495c9..936dd64679 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -43,6 +43,11 @@ namespace VFS return getNormalized(name); } + Files::IStreamPtr Manager::get(Path::NormalizedView name) const + { + return getNormalized(name.value()); + } + Files::IStreamPtr Manager::getNormalized(std::string_view normalizedName) const { assert(Path::isNormalized(normalizedName)); diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 955538627f..59211602de 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -50,6 +50,8 @@ namespace VFS /// @note May be called from any thread once the index has been built. Files::IStreamPtr get(const Path::Normalized& name) const; + Files::IStreamPtr get(Path::NormalizedView name) const; + /// Retrieve a file by name (name is already normalized). /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. From cdbe6adfc397ab2c3b454116c865bee67acd6255 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 12 Mar 2024 03:32:43 +0300 Subject: [PATCH 239/451] Fix instance selection mode destruction (#7447) --- CHANGELOG.md | 1 + apps/opencs/view/render/instanceselectionmode.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddbab574d5..443273c5ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,7 @@ Bug #7415: Unbreakable lock discrepancies Bug #7416: Modpccrimelevel is different from vanilla Bug #7428: AutoCalc flag is not used to calculate enchantment costs + Bug #7447: OpenMW-CS: Dragging a cell of a different type (from the initial type) into the 3D view crashes OpenMW-CS 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 diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp index fa8998747d..d3e2379640 100644 --- a/apps/opencs/view/render/instanceselectionmode.cpp +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -58,7 +58,8 @@ namespace CSVRender InstanceSelectionMode::~InstanceSelectionMode() { - mParentNode->removeChild(mBaseNode); + if (mBaseNode) + mParentNode->removeChild(mBaseNode); } void InstanceSelectionMode::setDragStart(const osg::Vec3d& dragStart) From 75d4ea5d5de89022c09fa24706ffd6d519af6144 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 12 Mar 2024 04:03:04 +0300 Subject: [PATCH 240/451] Replace readme 1.0 label link with 1.0 milestone link (#7876) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 95ca19685d..67ba2ce003 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Font Licenses: Current Status -------------- -The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://gitlab.com/OpenMW/openmw/issues?label_name%5B%5D=1.0) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces. +The main quests in Morrowind, Tribunal and Bloodmoon are all completable. Some issues with side quests are to be expected (but rare). Check the [bug tracker](https://gitlab.com/OpenMW/openmw/-/issues/?milestone_title=openmw-1.0) for a list of issues we need to resolve before the "1.0" release. Even before the "1.0" release however, OpenMW boasts some new [features](https://wiki.openmw.org/index.php?title=Features), such as improved graphics and user interfaces. Pre-existing modifications created for the original Morrowind engine can be hit-and-miss. The OpenMW script compiler performs more thorough error-checking than Morrowind does, meaning that a mod created for Morrowind may not necessarily run in OpenMW. Some mods also rely on quirky behaviour or engine bugs in order to work. We are considering such compatibility issues on a case-by-case basis - in some cases adding a workaround to OpenMW may be feasible, in other cases fixing the mod will be the only option. If you know of any mods that work or don't work, feel free to add them to the [Mod status](https://wiki.openmw.org/index.php?title=Mod_status) wiki page. From 59a25291f8a065a5525f46ba3974b43dc0fc0384 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 12 Mar 2024 07:29:48 -0500 Subject: [PATCH 241/451] Fix errors --- apps/openmw/mwlua/mwscriptbindings.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index c04339f28a..6ccb8c80fd 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -65,7 +65,7 @@ namespace MWLua return static_cast(MWBase::Environment::get().getWorld()->getGlobalInt(globalId)); } return 0; - }; + } sol::table initMWScriptBindings(const Context& context) { @@ -146,7 +146,7 @@ namespace MWLua return sol::nullopt; return getGlobalVariableValue(globalId); }, - [](const GlobalStore& store, int index) -> sol::optional { + [](const GlobalStore& store, size_t index) -> sol::optional { if (index < 1 || store.getSize() < index) return sol::nullopt; auto g = store.at(index - 1); @@ -164,7 +164,7 @@ namespace MWLua return getGlobalVariableValue(globalId); }); globalStoreT[sol::meta_function::pairs] = [](const GlobalStore& store) { - int index = 0; + size_t index = 0; return sol::as_function( [index, &store](sol::this_state ts) mutable -> sol::optional> { if (index >= store.getSize()) From b12f98db98dc0b28113bee42cda8b615fcbfd1f7 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 12 Mar 2024 17:46:38 +0100 Subject: [PATCH 242/451] Don't destroy root widget for new elements --- components/lua_ui/element.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index b491acb7b3..9d45f6ed7f 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -313,11 +313,14 @@ namespace LuaUi { if (mState != Destroyed) { - destroyRoot(mRoot); - mRoot = nullptr; if (mState != New) - mLayout = sol::make_object(mLayout.lua_state(), sol::nil); - mState = Destroyed; + { + assert(mRoot); + destroyRoot(mRoot); + mRoot = nullptr; + } + mLayout = sol::make_object(mLayout.lua_state(), sol::nil); } + mState = Destroyed; } } From f9da2b6b26ad8737f13732002c491694a32927ce Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Mar 2024 17:14:43 +0100 Subject: [PATCH 243/451] Roll for each region sound --- CHANGELOG.md | 1 + apps/openmw/mwsound/regionsoundselector.cpp | 38 +++------------------ apps/openmw/mwsound/regionsoundselector.hpp | 3 +- apps/openmw/mwsound/soundmanagerimp.cpp | 5 +-- 4 files changed, 10 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 953801b345..64f0f55fb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -157,6 +157,7 @@ Bug #7840: First run of the launcher doesn't save viewing distance as the default value Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs Bug #7859: AutoCalc flag is not used to calculate potion value + Bug #7872: Region sounds use wrong odds Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwsound/regionsoundselector.cpp b/apps/openmw/mwsound/regionsoundselector.cpp index 8fda57596a..89b5526d30 100644 --- a/apps/openmw/mwsound/regionsoundselector.cpp +++ b/apps/openmw/mwsound/regionsoundselector.cpp @@ -4,29 +4,18 @@ #include #include -#include -#include - #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" namespace MWSound { - namespace - { - int addChance(int result, const ESM::Region::SoundRef& v) - { - return result + v.mChance; - } - } - RegionSoundSelector::RegionSoundSelector() : mMinTimeBetweenSounds(Fallback::Map::getFloat("Weather_Minimum_Time_Between_Environmental_Sounds")) , mMaxTimeBetweenSounds(Fallback::Map::getFloat("Weather_Maximum_Time_Between_Environmental_Sounds")) { } - std::optional RegionSoundSelector::getNextRandom(float duration, const ESM::RefId& regionName) + ESM::RefId RegionSoundSelector::getNextRandom(float duration, const ESM::RefId& regionName) { mTimePassed += duration; @@ -49,28 +38,11 @@ namespace MWSound if (region == nullptr) return {}; - if (mSumChance == 0) + for (const ESM::Region::SoundRef& sound : region->mSoundList) { - mSumChance = std::accumulate(region->mSoundList.begin(), region->mSoundList.end(), 0, addChance); - if (mSumChance == 0) - return {}; + if (Misc::Rng::roll0to99() < sound.mChance) + return sound.mSound; } - - const int r = Misc::Rng::rollDice(std::max(mSumChance, 100)); - int pos = 0; - - const auto isSelected = [&](const ESM::Region::SoundRef& sound) { - if (r - pos < sound.mChance) - return true; - pos += sound.mChance; - return false; - }; - - const auto it = std::find_if(region->mSoundList.begin(), region->mSoundList.end(), isSelected); - - if (it == region->mSoundList.end()) - return {}; - - return it->mSound; + return {}; } } diff --git a/apps/openmw/mwsound/regionsoundselector.hpp b/apps/openmw/mwsound/regionsoundselector.hpp index 1a9e6e450b..7a7659f56d 100644 --- a/apps/openmw/mwsound/regionsoundselector.hpp +++ b/apps/openmw/mwsound/regionsoundselector.hpp @@ -2,7 +2,6 @@ #define GAME_SOUND_REGIONSOUNDSELECTOR_H #include -#include #include namespace MWBase @@ -15,7 +14,7 @@ namespace MWSound class RegionSoundSelector { public: - std::optional getNextRandom(float duration, const ESM::RefId& regionName); + ESM::RefId getNextRandom(float duration, const ESM::RefId& regionName); RegionSoundSelector(); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 3658be4819..56224b4dcb 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -900,8 +900,9 @@ namespace MWSound if (mCurrentRegionSound && mOutput->isSoundPlaying(mCurrentRegionSound)) return; - if (const auto next = mRegionSoundSelector.getNextRandom(duration, cell->getRegion())) - mCurrentRegionSound = playSound(*next, 1.0f, 1.0f); + ESM::RefId next = mRegionSoundSelector.getNextRandom(duration, cell->getRegion()); + if (!next.empty()) + mCurrentRegionSound = playSound(next, 1.0f, 1.0f); } void SoundManager::updateWaterSound() From 1d69d3808179c326255a7728f8e2e9e01dcb0aed Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Mar 2024 17:14:49 +0100 Subject: [PATCH 244/451] Add an actual probability column --- apps/opencs/model/world/columns.cpp | 1 + apps/opencs/model/world/columns.hpp | 2 ++ apps/opencs/model/world/data.cpp | 6 ++++-- .../model/world/nestedcoladapterimp.cpp | 21 ++++++++++++++----- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 45759cd234..f487266dbb 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -235,6 +235,7 @@ namespace CSMWorld { ColumnId_RegionSounds, "Sounds" }, { ColumnId_SoundName, "Sound Name" }, { ColumnId_SoundChance, "Chance" }, + { ColumnId_SoundProbability, "Probability" }, { ColumnId_FactionReactions, "Reactions" }, { ColumnId_FactionRanks, "Ranks" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 74e5bdd006..f5a8e446a5 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -349,6 +349,8 @@ namespace CSMWorld ColumnId_SelectionGroupObjects = 316, + ColumnId_SoundProbability = 317, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index ba1f1e5ac3..7bee635678 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -301,8 +301,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mRegions.addColumn(new NestedParentColumn(Columns::ColumnId_RegionWeather)); index = mRegions.getColumns() - 1; mRegions.addAdapter(std::make_pair(&mRegions.getColumn(index), new RegionWeatherAdapter())); - mRegions.getNestableColumn(index)->addColumn( - new NestedChildColumn(Columns::ColumnId_WeatherName, ColumnBase::Display_String, false)); + mRegions.getNestableColumn(index)->addColumn(new NestedChildColumn( + Columns::ColumnId_WeatherName, ColumnBase::Display_String, ColumnBase::Flag_Dialogue, false)); mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn(Columns::ColumnId_WeatherChance, ColumnBase::Display_UnsignedInteger8)); // Region Sounds @@ -313,6 +313,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data new NestedChildColumn(Columns::ColumnId_SoundName, ColumnBase::Display_Sound)); mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn(Columns::ColumnId_SoundChance, ColumnBase::Display_UnsignedInteger8)); + mRegions.getNestableColumn(index)->addColumn(new NestedChildColumn( + Columns::ColumnId_SoundProbability, ColumnBase::Display_Float, ColumnBase::Flag_Dialogue, false)); mBirthsigns.addColumn(new StringIdColumn); mBirthsigns.addColumn(new RecordStateColumn); diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 8b8c7b17be..9e5363e606 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -414,20 +414,31 @@ namespace CSMWorld QVariant RegionSoundListAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { - ESM::Region region = record.get(); + const ESM::Region& region = record.get(); - std::vector& soundList = region.mSoundList; + const std::vector& soundList = region.mSoundList; - if (subRowIndex < 0 || subRowIndex >= static_cast(soundList.size())) + const size_t index = static_cast(subRowIndex); + if (subRowIndex < 0 || index >= soundList.size()) throw std::runtime_error("index out of range"); - ESM::Region::SoundRef soundRef = soundList[subRowIndex]; + const ESM::Region::SoundRef& soundRef = soundList[subRowIndex]; switch (subColIndex) { case 0: return QString(soundRef.mSound.getRefIdString().c_str()); case 1: return soundRef.mChance; + case 2: + { + float probability = 1.f; + for (size_t i = 0; i < index; ++i) + { + const float p = std::min(soundList[i].mChance / 100.f, 1.f); + probability *= 1.f - p; + } + return probability * std::min(soundRef.mChance / 100.f, 1.f) * 100.f; + } default: throw std::runtime_error("Region sounds subcolumn index out of range"); } @@ -463,7 +474,7 @@ namespace CSMWorld int RegionSoundListAdapter::getColumnsCount(const Record& record) const { - return 2; + return 3; } int RegionSoundListAdapter::getRowsCount(const Record& record) const From c0578613af3c3be9c268cc51db4387461908c29c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 10 Mar 2024 16:58:40 +0100 Subject: [PATCH 245/451] Remove superfluous members --- apps/openmw/mwsound/regionsoundselector.cpp | 8 +------- apps/openmw/mwsound/regionsoundselector.hpp | 8 -------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/apps/openmw/mwsound/regionsoundselector.cpp b/apps/openmw/mwsound/regionsoundselector.cpp index 89b5526d30..cb2ece7f8f 100644 --- a/apps/openmw/mwsound/regionsoundselector.cpp +++ b/apps/openmw/mwsound/regionsoundselector.cpp @@ -26,14 +26,8 @@ namespace MWSound mTimeToNextEnvSound = mMinTimeBetweenSounds + (mMaxTimeBetweenSounds - mMinTimeBetweenSounds) * a; mTimePassed = 0; - if (mLastRegionName != regionName) - { - mLastRegionName = regionName; - mSumChance = 0; - } - const ESM::Region* const region - = MWBase::Environment::get().getESMStore()->get().search(mLastRegionName); + = MWBase::Environment::get().getESMStore()->get().search(regionName); if (region == nullptr) return {}; diff --git a/apps/openmw/mwsound/regionsoundselector.hpp b/apps/openmw/mwsound/regionsoundselector.hpp index 7a7659f56d..474e1afa06 100644 --- a/apps/openmw/mwsound/regionsoundselector.hpp +++ b/apps/openmw/mwsound/regionsoundselector.hpp @@ -2,12 +2,6 @@ #define GAME_SOUND_REGIONSOUNDSELECTOR_H #include -#include - -namespace MWBase -{ - class World; -} namespace MWSound { @@ -20,8 +14,6 @@ namespace MWSound private: float mTimeToNextEnvSound = 0.0f; - int mSumChance = 0; - ESM::RefId mLastRegionName; float mTimePassed = 0.0; float mMinTimeBetweenSounds; float mMaxTimeBetweenSounds; From 0fdc432eb243c7e9f53f7c329411e65e52d4515b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 10 Mar 2024 22:14:15 +0100 Subject: [PATCH 246/451] Format probability --- apps/opencs/model/world/data.cpp | 2 +- apps/opencs/model/world/nestedcoladapterimp.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 7bee635678..1f8ff54e89 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -314,7 +314,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mRegions.getNestableColumn(index)->addColumn( new NestedChildColumn(Columns::ColumnId_SoundChance, ColumnBase::Display_UnsignedInteger8)); mRegions.getNestableColumn(index)->addColumn(new NestedChildColumn( - Columns::ColumnId_SoundProbability, ColumnBase::Display_Float, ColumnBase::Flag_Dialogue, false)); + Columns::ColumnId_SoundProbability, ColumnBase::Display_String, ColumnBase::Flag_Dialogue, false)); mBirthsigns.addColumn(new StringIdColumn); mBirthsigns.addColumn(new RecordStateColumn); diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 9e5363e606..aa0178fd28 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -437,7 +437,8 @@ namespace CSMWorld const float p = std::min(soundList[i].mChance / 100.f, 1.f); probability *= 1.f - p; } - return probability * std::min(soundRef.mChance / 100.f, 1.f) * 100.f; + probability *= std::min(soundRef.mChance / 100.f, 1.f) * 100.f; + return QString("%1%").arg(probability, 0, 'f', 2); } default: throw std::runtime_error("Region sounds subcolumn index out of range"); From 942eeb54c1a80b5567fd08eb9779d1dcb06835da Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 12 Mar 2024 23:30:11 +0000 Subject: [PATCH 247/451] Yet another osgpluginchecker rewrite It turns out that it's possible for OSG plugins to be spread across multiple directories, and OSG doesn't account for this in osgDB::listAllAvailablePlugins(), even though it works when actually loading the plugin. Instead, use code that's much more similar to how OSG actually loads plugin, and therefore less likely to miss anything. Incidentally make things much simpler as we don't need awkwardness from working around osgDB::listAllAvailablePlugins()'s limitations. --- components/misc/osgpluginchecker.cpp.in | 44 +++---------------------- 1 file changed, 4 insertions(+), 40 deletions(-) diff --git a/components/misc/osgpluginchecker.cpp.in b/components/misc/osgpluginchecker.cpp.in index 81ae73f9e3..8e57d9a5ce 100644 --- a/components/misc/osgpluginchecker.cpp.in +++ b/components/misc/osgpluginchecker.cpp.in @@ -35,50 +35,14 @@ namespace Misc bool checkRequiredOSGPluginsArePresent() { - // work around osgDB::listAllAvailablePlugins() not working on some platforms due to a suspected OSG bug - std::filesystem::path pluginDirectoryName = std::string("osgPlugins-") + std::string(osgGetVersion()); - osgDB::FilePathList& filepath = osgDB::getLibraryFilePathList(); - for (const auto& path : filepath) - { -#ifdef OSG_USE_UTF8_FILENAME - std::filesystem::path osgPath{ StringUtils::stringToU8String(path) }; -#else - std::filesystem::path osgPath{ path }; -#endif - if (!osgPath.has_filename()) - osgPath = osgPath.parent_path(); - - if (osgPath.filename() == pluginDirectoryName) - { - osgPath = osgPath.parent_path(); -#ifdef OSG_USE_UTF8_FILENAME - std::string extraPath = StringUtils::u8StringToString(osgPath.u8string()); -#else - std::string extraPath = osgPath.string(); -#endif - filepath.emplace_back(std::move(extraPath)); - } - } - - auto availableOSGPlugins = osgDB::listAllAvailablePlugins(); + // osgDB::listAllAvailablePlugins() lies, so don't use it bool haveAllPlugins = true; for (std::string_view plugin : USED_OSG_PLUGIN_NAMES) { - if (std::find_if(availableOSGPlugins.begin(), availableOSGPlugins.end(), - [&](std::string_view availablePlugin) { -#ifdef OSG_USE_UTF8_FILENAME - std::filesystem::path pluginPath{ StringUtils::stringToU8String(availablePlugin) }; -#else - std::filesystem::path pluginPath {availablePlugin}; -#endif - return pluginPath.filename() - == std::filesystem::path( - osgDB::Registry::instance()->createLibraryNameForExtension(std::string{ plugin })) - .filename(); - }) - == availableOSGPlugins.end()) + std::string libraryName = osgDB::Registry::instance()->createLibraryNameForExtension(std::string{ plugin }); + if (osgDB::findLibraryFile(libraryName).empty()) { - Log(Debug::Error) << "Missing OSG plugin: " << plugin; + Log(Debug::Error) << "Missing OSG plugin: " << libraryName; haveAllPlugins = false; } } From 715efe892f8429960258d3b953c4c77781e200c4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 5 Mar 2024 10:07:35 +0400 Subject: [PATCH 248/451] Load YAML files via Lua (feature 7590) --- CHANGELOG.md | 1 + CI/file_name_exceptions.txt | 1 + CMakeLists.txt | 2 +- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/luabindings.cpp | 2 + apps/openmw/mwlua/markupbindings.cpp | 32 ++ apps/openmw/mwlua/markupbindings.hpp | 13 + apps/openmw_test_suite/CMakeLists.txt | 1 + apps/openmw_test_suite/lua/test_yaml.cpp | 354 ++++++++++++++++++ components/CMakeLists.txt | 2 +- components/lua/yamlloader.cpp | 241 ++++++++++++ components/lua/yamlloader.hpp | 50 +++ docs/source/reference/lua-scripting/api.rst | 1 + .../reference/lua-scripting/openmw_markup.rst | 7 + .../lua-scripting/tables/packages.rst | 2 + files/data/scripts/omw/console/global.lua | 1 + files/data/scripts/omw/console/local.lua | 1 + files/data/scripts/omw/console/menu.lua | 1 + files/data/scripts/omw/console/player.lua | 1 + files/lua_api/CMakeLists.txt | 1 + files/lua_api/openmw/markup.lua | 37 ++ 21 files changed, 750 insertions(+), 3 deletions(-) create mode 100644 apps/openmw/mwlua/markupbindings.cpp create mode 100644 apps/openmw/mwlua/markupbindings.hpp create mode 100644 apps/openmw_test_suite/lua/test_yaml.cpp create mode 100644 components/lua/yamlloader.cpp create mode 100644 components/lua/yamlloader.hpp create mode 100644 docs/source/reference/lua-scripting/openmw_markup.rst create mode 100644 files/lua_api/openmw/markup.lua diff --git a/CHANGELOG.md b/CHANGELOG.md index fe081224ce..f1f36e594c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -194,6 +194,7 @@ Feature #7546: Start the game on Fredas Feature #7554: Controller binding for tab for menu navigation Feature #7568: Uninterruptable scripted music + Feature #7590: [Lua] Ability to deserialize YAML data from scripts Feature #7606: Launcher: allow Shift-select in Archives tab Feature #7608: Make the missing dependencies warning when loading a savegame more helpful Feature #7618: Show the player character's health in the save details diff --git a/CI/file_name_exceptions.txt b/CI/file_name_exceptions.txt index c3bcee8661..dff3527348 100644 --- a/CI/file_name_exceptions.txt +++ b/CI/file_name_exceptions.txt @@ -20,6 +20,7 @@ apps/openmw_test_suite/lua/test_storage.cpp apps/openmw_test_suite/lua/test_ui_content.cpp apps/openmw_test_suite/lua/test_utilpackage.cpp apps/openmw_test_suite/lua/test_inputactions.cpp +apps/openmw_test_suite/lua/test_yaml.cpp apps/openmw_test_suite/misc/test_endianness.cpp apps/openmw_test_suite/misc/test_resourcehelpers.cpp apps/openmw_test_suite/misc/test_stringops.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ca25fd05ff..cfcd167cd5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,7 +80,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 56) +set(OPENMW_LUA_API_REVISION 57) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 5fb06881ec..08bf11d194 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -64,7 +64,7 @@ add_openmw_dir (mwlua context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings - classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings + classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings markupbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 0de10827e0..553b8af8f6 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -14,6 +14,7 @@ #include "debugbindings.hpp" #include "inputbindings.hpp" #include "localscripts.hpp" +#include "markupbindings.hpp" #include "menuscripts.hpp" #include "nearbybindings.hpp" #include "objectbindings.hpp" @@ -35,6 +36,7 @@ namespace MWLua { "openmw.async", LuaUtil::getAsyncPackageInitializer( lua, [tm] { return tm->getSimulationTime(); }, [tm] { return tm->getGameTime(); }) }, + { "openmw.markup", initMarkupPackage(context) }, { "openmw.util", LuaUtil::initUtilPackage(lua) }, { "openmw.vfs", initVFSPackage(context) }, }; diff --git a/apps/openmw/mwlua/markupbindings.cpp b/apps/openmw/mwlua/markupbindings.cpp new file mode 100644 index 0000000000..997674b45d --- /dev/null +++ b/apps/openmw/mwlua/markupbindings.cpp @@ -0,0 +1,32 @@ +#include "markupbindings.hpp" + +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" + +#include "context.hpp" + +namespace MWLua +{ + sol::table initMarkupPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + api["loadYaml"] = [lua = context.mLua, vfs](std::string_view fileName) { + auto normalizedName = VFS::Path::normalizeFilename(fileName); + auto file = vfs->getNormalized(normalizedName); + return LuaUtil::YamlLoader::load(*file, lua->sol()); + }; + api["decodeYaml"] = [lua = context.mLua](std::string_view inputData) { + return LuaUtil::YamlLoader::load(std::string(inputData), lua->sol()); + }; + + return LuaUtil::makeReadOnly(api); + } +} diff --git a/apps/openmw/mwlua/markupbindings.hpp b/apps/openmw/mwlua/markupbindings.hpp new file mode 100644 index 0000000000..9105ab5edf --- /dev/null +++ b/apps/openmw/mwlua/markupbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_MARKUPBINDINGS_H +#define MWLUA_MARKUPBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initMarkupPackage(const Context&); +} + +#endif // MWLUA_MARKUPBINDINGS_H diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 71da2de590..f3f50cea71 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -29,6 +29,7 @@ file(GLOB UNITTEST_SRC_FILES lua/test_storage.cpp lua/test_async.cpp lua/test_inputactions.cpp + lua/test_yaml.cpp lua/test_ui_content.cpp diff --git a/apps/openmw_test_suite/lua/test_yaml.cpp b/apps/openmw_test_suite/lua/test_yaml.cpp new file mode 100644 index 0000000000..c7d484cf51 --- /dev/null +++ b/apps/openmw_test_suite/lua/test_yaml.cpp @@ -0,0 +1,354 @@ +#include "gmock/gmock.h" +#include + +#include + +#include "../testing_util.hpp" + +namespace +{ + template + bool checkNumber(sol::state_view& lua, const std::string& inputData, T requiredValue) + { + sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + if (result.get_type() != sol::type::number) + return false; + + return result.as() == requiredValue; + } + + bool checkBool(sol::state_view& lua, const std::string& inputData, bool requiredValue) + { + sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + if (result.get_type() != sol::type::boolean) + return false; + + return result.as() == requiredValue; + } + + bool checkNil(sol::state_view& lua, const std::string& inputData) + { + sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + return result == sol::nil; + } + + bool checkNan(sol::state_view& lua, const std::string& inputData) + { + sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + if (result.get_type() != sol::type::number) + return false; + + return std::isnan(result.as()); + } + + bool checkString(sol::state_view& lua, const std::string& inputData, const std::string& requiredValue) + { + sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + if (result.get_type() != sol::type::string) + return false; + + return result.as() == requiredValue; + } + + bool checkString(sol::state_view& lua, const std::string& inputData) + { + sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + if (result.get_type() != sol::type::string) + return false; + + return result.as() == inputData; + } + + TEST(LuaUtilYamlLoader, ScalarTypeDeduction) + { + sol::state lua; + + ASSERT_TRUE(checkNil(lua, "null")); + ASSERT_TRUE(checkNil(lua, "Null")); + ASSERT_TRUE(checkNil(lua, "NULL")); + ASSERT_TRUE(checkNil(lua, "~")); + ASSERT_TRUE(checkNil(lua, "")); + ASSERT_FALSE(checkNil(lua, "NUll")); + ASSERT_TRUE(checkString(lua, "NUll")); + ASSERT_TRUE(checkString(lua, "'null'", "null")); + + ASSERT_TRUE(checkNumber(lua, "017", 17)); + ASSERT_TRUE(checkNumber(lua, "-017", -17)); + ASSERT_TRUE(checkNumber(lua, "+017", 17)); + ASSERT_TRUE(checkNumber(lua, "17", 17)); + ASSERT_TRUE(checkNumber(lua, "-17", -17)); + ASSERT_TRUE(checkNumber(lua, "+17", 17)); + ASSERT_TRUE(checkNumber(lua, "0o17", 15)); + ASSERT_TRUE(checkString(lua, "-0o17")); + ASSERT_TRUE(checkString(lua, "+0o17")); + ASSERT_TRUE(checkString(lua, "0b1")); + ASSERT_TRUE(checkString(lua, "1:00")); + ASSERT_TRUE(checkString(lua, "'17'", "17")); + ASSERT_TRUE(checkNumber(lua, "0x17", 23)); + ASSERT_TRUE(checkString(lua, "'-0x17'", "-0x17")); + ASSERT_TRUE(checkString(lua, "'+0x17'", "+0x17")); + + ASSERT_TRUE(checkNumber(lua, "2.1e-05", 2.1e-5)); + ASSERT_TRUE(checkNumber(lua, "-2.1e-05", -2.1e-5)); + ASSERT_TRUE(checkNumber(lua, "+2.1e-05", 2.1e-5)); + ASSERT_TRUE(checkNumber(lua, "2.1e+5", 210000)); + ASSERT_TRUE(checkNumber(lua, "-2.1e+5", -210000)); + ASSERT_TRUE(checkNumber(lua, "+2.1e+5", 210000)); + ASSERT_TRUE(checkNumber(lua, "0.27", 0.27)); + ASSERT_TRUE(checkNumber(lua, "-0.27", -0.27)); + ASSERT_TRUE(checkNumber(lua, "+0.27", 0.27)); + ASSERT_TRUE(checkNumber(lua, "2.7", 2.7)); + ASSERT_TRUE(checkNumber(lua, "-2.7", -2.7)); + ASSERT_TRUE(checkNumber(lua, "+2.7", 2.7)); + ASSERT_TRUE(checkNumber(lua, ".27", 0.27)); + ASSERT_TRUE(checkNumber(lua, "-.27", -0.27)); + ASSERT_TRUE(checkNumber(lua, "+.27", 0.27)); + ASSERT_TRUE(checkNumber(lua, "27.", 27.0)); + ASSERT_TRUE(checkNumber(lua, "-27.", -27.0)); + ASSERT_TRUE(checkNumber(lua, "+27.", 27.0)); + + ASSERT_TRUE(checkNan(lua, ".nan")); + ASSERT_TRUE(checkNan(lua, ".NaN")); + ASSERT_TRUE(checkNan(lua, ".NAN")); + ASSERT_FALSE(checkNan(lua, "nan")); + ASSERT_FALSE(checkNan(lua, ".nAn")); + ASSERT_TRUE(checkString(lua, "'.nan'", ".nan")); + ASSERT_TRUE(checkString(lua, ".nAn")); + + ASSERT_TRUE(checkNumber(lua, "1.7976931348623157E+308", std::numeric_limits::max())); + ASSERT_TRUE(checkNumber(lua, "-1.7976931348623157E+308", std::numeric_limits::lowest())); + ASSERT_TRUE(checkNumber(lua, "2.2250738585072014e-308", std::numeric_limits::min())); + ASSERT_TRUE(checkNumber(lua, ".inf", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "+.inf", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "-.inf", -std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, ".Inf", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "+.Inf", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "-.Inf", -std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, ".INF", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "+.INF", std::numeric_limits::infinity())); + ASSERT_TRUE(checkNumber(lua, "-.INF", -std::numeric_limits::infinity())); + ASSERT_TRUE(checkString(lua, ".INf")); + ASSERT_TRUE(checkString(lua, "-.INf")); + ASSERT_TRUE(checkString(lua, "+.INf")); + + ASSERT_TRUE(checkBool(lua, "true", true)); + ASSERT_TRUE(checkBool(lua, "false", false)); + ASSERT_TRUE(checkBool(lua, "True", true)); + ASSERT_TRUE(checkBool(lua, "False", false)); + ASSERT_TRUE(checkBool(lua, "TRUE", true)); + ASSERT_TRUE(checkBool(lua, "FALSE", false)); + ASSERT_TRUE(checkString(lua, "y")); + ASSERT_TRUE(checkString(lua, "n")); + ASSERT_TRUE(checkString(lua, "On")); + ASSERT_TRUE(checkString(lua, "Off")); + ASSERT_TRUE(checkString(lua, "YES")); + ASSERT_TRUE(checkString(lua, "NO")); + ASSERT_TRUE(checkString(lua, "TrUe")); + ASSERT_TRUE(checkString(lua, "FaLsE")); + ASSERT_TRUE(checkString(lua, "'true'", "true")); + } + + TEST(LuaUtilYamlLoader, DepthLimit) + { + sol::state lua; + + const std::string input = R"( + array1: &array1_alias + [ + <: *array1_alias, + foo + ] + )"; + + bool depthExceptionThrown = false; + try + { + YAML::Node root = YAML::Load(input); + sol::object result = LuaUtil::YamlLoader::load(input, lua); + } + catch (const std::runtime_error& e) + { + ASSERT_EQ(std::string(e.what()), "Maximum layers depth exceeded, probably caused by a circular reference"); + depthExceptionThrown = true; + } + + ASSERT_TRUE(depthExceptionThrown); + } + + TEST(LuaUtilYamlLoader, Collections) + { + sol::state lua; + + sol::object map = LuaUtil::YamlLoader::load("{ x: , y: 2, 4: 5 }", lua); + ASSERT_EQ(map.as()["x"], sol::nil); + ASSERT_EQ(map.as()["y"], 2); + ASSERT_EQ(map.as()[4], 5); + + sol::object array = LuaUtil::YamlLoader::load("[ 3, 4 ]", lua); + ASSERT_EQ(array.as()[1], 3); + + sol::object emptyTable = LuaUtil::YamlLoader::load("{}", lua); + ASSERT_TRUE(emptyTable.as().empty()); + + sol::object emptyArray = LuaUtil::YamlLoader::load("[]", lua); + ASSERT_TRUE(emptyArray.as().empty()); + + ASSERT_THROW(LuaUtil::YamlLoader::load("{ null: 1 }", lua), std::runtime_error); + ASSERT_THROW(LuaUtil::YamlLoader::load("{ .nan: 1 }", lua), std::runtime_error); + + const std::string scalarArrayInput = R"( + - First Scalar + - 1 + - true)"; + + sol::object scalarArray = LuaUtil::YamlLoader::load(scalarArrayInput, lua); + ASSERT_EQ(scalarArray.as()[1], std::string("First Scalar")); + ASSERT_EQ(scalarArray.as()[2], 1); + ASSERT_EQ(scalarArray.as()[3], true); + + const std::string scalarMapWithCommentsInput = R"( + string: 'str' # String value + integer: 65 # Integer value + float: 0.278 # Float value + bool: false # Boolean value)"; + + sol::object scalarMapWithComments = LuaUtil::YamlLoader::load(scalarMapWithCommentsInput, lua); + ASSERT_EQ(scalarMapWithComments.as()["string"], std::string("str")); + ASSERT_EQ(scalarMapWithComments.as()["integer"], 65); + ASSERT_EQ(scalarMapWithComments.as()["float"], 0.278); + ASSERT_EQ(scalarMapWithComments.as()["bool"], false); + + const std::string mapOfArraysInput = R"( + x: + - 2 + - 7 + - true + y: + - aaa + - false + - 1)"; + + sol::object mapOfArrays = LuaUtil::YamlLoader::load(mapOfArraysInput, lua); + ASSERT_EQ(mapOfArrays.as()["x"][3], true); + ASSERT_EQ(mapOfArrays.as()["y"][1], std::string("aaa")); + + const std::string arrayOfMapsInput = R"( + - + name: Name1 + hr: 65 + avg: 0.278 + - + name: Name2 + hr: 63 + avg: 0.288)"; + + sol::object arrayOfMaps = LuaUtil::YamlLoader::load(arrayOfMapsInput, lua); + ASSERT_EQ(arrayOfMaps.as()[1]["avg"], 0.278); + ASSERT_EQ(arrayOfMaps.as()[2]["name"], std::string("Name2")); + + const std::string arrayOfArraysInput = R"( + - [Name1, 65, 0.278] + - [Name2 , 63, 0.288])"; + + sol::object arrayOfArrays = LuaUtil::YamlLoader::load(arrayOfArraysInput, lua); + ASSERT_EQ(arrayOfArrays.as()[1][2], 65); + ASSERT_EQ(arrayOfArrays.as()[2][1], std::string("Name2")); + + const std::string mapOfMapsInput = R"( + Name1: {hr: 65, avg: 0.278} + Name2 : { + hr: 63, + avg: 0.288, + })"; + + sol::object mapOfMaps = LuaUtil::YamlLoader::load(mapOfMapsInput, lua); + ASSERT_EQ(mapOfMaps.as()["Name1"]["hr"], 65); + ASSERT_EQ(mapOfMaps.as()["Name2"]["avg"], 0.288); + } + + TEST(LuaUtilYamlLoader, Structures) + { + sol::state lua; + + const std::string twoDocumentsInput + = "---\n" + " - First Scalar\n" + " - 2\n" + " - true\n" + "\n" + "---\n" + " - Second Scalar\n" + " - 3\n" + " - false"; + + sol::object twoDocuments = LuaUtil::YamlLoader::load(twoDocumentsInput, lua); + ASSERT_EQ(twoDocuments.as()[1][1], std::string("First Scalar")); + ASSERT_EQ(twoDocuments.as()[2][3], false); + + const std::string anchorInput = R"(--- + x: + - Name1 + # Following node labeled as "a" + - &a Value1 + y: + - *a # Subsequent occurrence + - Name2)"; + + sol::object anchor = LuaUtil::YamlLoader::load(anchorInput, lua); + ASSERT_EQ(anchor.as()["y"][1], std::string("Value1")); + + const std::string compoundKeyInput = R"( + ? - String1 + - String2 + : - 1 + + ? [ String3, + String4 ] + : [ 2, 3, 4 ])"; + + ASSERT_THROW(LuaUtil::YamlLoader::load(compoundKeyInput, lua), std::runtime_error); + + const std::string compactNestedMappingInput = R"( + - item : Item1 + quantity: 2 + - item : Item2 + quantity: 4 + - item : Item3 + quantity: 11)"; + + sol::object compactNestedMapping = LuaUtil::YamlLoader::load(compactNestedMappingInput, lua); + ASSERT_EQ(compactNestedMapping.as()[2]["quantity"], 4); + } + + TEST(LuaUtilYamlLoader, Scalars) + { + sol::state lua; + + const std::string literalScalarInput = R"(--- | + a + b + c)"; + + ASSERT_TRUE(checkString(lua, literalScalarInput, "a\nb\nc")); + + const std::string foldedScalarInput = R"(--- > + a + b + c)"; + + ASSERT_TRUE(checkString(lua, foldedScalarInput, "a b c")); + + const std::string multiLinePlanarScalarsInput = R"( + plain: + This unquoted scalar + spans many lines. + + quoted: "So does this + quoted scalar.\n")"; + + sol::object multiLinePlanarScalars = LuaUtil::YamlLoader::load(multiLinePlanarScalarsInput, lua); + ASSERT_TRUE( + multiLinePlanarScalars.as()["plain"] == std::string("This unquoted scalar spans many lines.")); + ASSERT_TRUE(multiLinePlanarScalars.as()["quoted"] == std::string("So does this quoted scalar.\n")); + } +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 01efcd7c05..f593e0f0f2 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -65,7 +65,7 @@ list(APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${OSG_PLUGIN_CHECKER_CPP_FILE} add_component_dir (lua luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8 - shapes/box inputactions + shapes/box inputactions yamlloader ) add_component_dir (l10n diff --git a/components/lua/yamlloader.cpp b/components/lua/yamlloader.cpp new file mode 100644 index 0000000000..14553cfac4 --- /dev/null +++ b/components/lua/yamlloader.cpp @@ -0,0 +1,241 @@ +#include "yamlloader.hpp" + +#include +#include + +#include +#include + +namespace LuaUtil +{ + namespace + { + constexpr uint64_t maxDepth = 250; + } + + sol::object YamlLoader::load(const std::string& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return LuaUtil::YamlLoader::load(rootNodes, lua); + } + + sol::object YamlLoader::load(std::istream& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return load(rootNodes, lua); + } + + sol::object YamlLoader::load(const std::vector rootNodes, const sol::state_view& lua) + { + if (rootNodes.empty()) + return sol::nil; + + if (rootNodes.size() == 1) + return getNode(rootNodes[0], lua, 0); + + sol::table documentsTable(lua, sol::create); + for (const auto& root : rootNodes) + { + documentsTable.add(getNode(root, lua, 1)); + } + + return documentsTable; + } + + sol::object YamlLoader::getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + if (depth >= maxDepth) + throw std::runtime_error("Maximum layers depth exceeded, probably caused by a circular reference"); + + ++depth; + + if (node.IsMap()) + return getMap(node, lua, depth); + else if (node.IsSequence()) + return getArray(node, lua, depth); + else if (node.IsScalar()) + return getScalar(node, lua); + else if (node.IsNull()) + return sol::nil; + + nodeError(node, "An unknown YAML node encountered"); + } + + sol::table YamlLoader::getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + sol::table childTable(lua, sol::create); + + for (const auto& pair : node) + { + if (pair.first.IsMap()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered map instead"); + if (pair.first.IsSequence()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered array instead"); + if (pair.first.IsNull()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered null instead"); + + auto key = getNode(pair.first, lua, depth); + if (key.get_type() == sol::type::number && std::isnan(key.as())) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered nan instead"); + + childTable[key] = getNode(pair.second, lua, depth); + } + + return childTable; + } + + sol::table YamlLoader::getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + sol::table childTable(lua, sol::create); + + for (const auto& child : node) + { + childTable.add(getNode(child, lua, depth)); + } + + return childTable; + } + + YamlLoader::ScalarType YamlLoader::getScalarType(const YAML::Node& node) + { + const auto& tag = node.Tag(); + const auto& value = node.Scalar(); + if (tag == "!") + return ScalarType::String; + + // Note that YAML allows to explicitely specify a scalar type via tag (e.g. "!!bool"), but it makes no + // sense in Lua: + // 1. Both integers and floats use the "number" type prior to Lua 5.3 + // 2. Strings can be quoted, which is more readable than "!!str" + // 3. Most of possible conversions are invalid or their result is unclear + // So ignore this feature for now. + if (tag != "?") + nodeError(node, "An invalid tag'" + tag + "' encountered"); + + if (value.empty()) + return ScalarType::Null; + + // Resolve type according to YAML 1.2 Core Schema (see https://yaml.org/spec/1.2.2/#103-core-schema) + static const std::regex boolRegex("true|True|TRUE|false|False|FALSE", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), boolRegex)) + return ScalarType::Boolean; + + static const std::regex decimalRegex("[-+]?[0-9]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), decimalRegex)) + return ScalarType::Decimal; + + static const std::regex floatRegex( + "[-+]?([.][0-9]+|[0-9]+([.][0-9]*)?)([eE][-+]?[0-9]+)?", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), floatRegex)) + return ScalarType::Float; + + static const std::regex octalRegex("0o[0-7]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), octalRegex)) + return ScalarType::Octal; + + static const std::regex hexdecimalRegex("0x[0-9a-fA-F]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), hexdecimalRegex)) + return ScalarType::Hexadecimal; + + static const std::regex infinityRegex("[-+]?([.]inf|[.]Inf|[.]INF)", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), infinityRegex)) + return ScalarType::Infinity; + + static const std::regex nanRegex("[.]nan|[.]NaN|[.]NAN", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), nanRegex)) + return ScalarType::NotNumber; + + static const std::regex nullRegex("null|Null|NULL|~", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), nullRegex)) + return ScalarType::Null; + + return ScalarType::String; + } + + sol::object YamlLoader::getScalar(const YAML::Node& node, const sol::state_view& lua) + { + auto type = getScalarType(node); + const auto& value = node.Scalar(); + + switch (type) + { + case ScalarType::Null: + return sol::nil; + case ScalarType::String: + return sol::make_object(lua, value); + case ScalarType::NotNumber: + return sol::make_object(lua, std::nan("")); + case ScalarType::Infinity: + { + if (!value.empty() && value[0] == '-') + return sol::make_object(lua, -std::numeric_limits::infinity()); + + return sol::make_object(lua, std::numeric_limits::infinity()); + } + case ScalarType::Boolean: + { + if (Misc::StringUtils::lowerCase(value) == "true") + return sol::make_object(lua, true); + + if (Misc::StringUtils::lowerCase(value) == "false") + return sol::make_object(lua, false); + + nodeError(node, "Can not read a boolean value '" + value + "'"); + } + case ScalarType::Decimal: + { + int offset = 0; + + // std::from_chars does not support "+" sign + if (!value.empty() && value[0] == '+') + ++offset; + + int result = 0; + const auto status = std::from_chars(value.data() + offset, value.data() + value.size(), result); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a decimal value '" + value + "'"); + } + case ScalarType::Float: + { + // Not all compilers support std::from_chars for floats + double result = 0.0; + bool success = YAML::convert::decode(node, result); + if (success) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a float value '" + value + "'"); + } + case ScalarType::Hexadecimal: + { + int result = 0; + const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 16); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a hexadecimal value '" + value + "'"); + } + case ScalarType::Octal: + { + int result = 0; + const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 8); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read an octal value '" + value + "'"); + } + default: + nodeError(node, "An unknown scalar '" + value + "' encountered"); + } + } + + [[noreturn]] void YamlLoader::nodeError(const YAML::Node& node, const std::string& message) + { + const auto& mark = node.Mark(); + std::string error = Misc::StringUtils::format( + " at line=%d column=%d position=%d", mark.line + 1, mark.column + 1, mark.pos + 1); + throw std::runtime_error(message + error); + } + +} diff --git a/components/lua/yamlloader.hpp b/components/lua/yamlloader.hpp new file mode 100644 index 0000000000..1ca95223cd --- /dev/null +++ b/components/lua/yamlloader.hpp @@ -0,0 +1,50 @@ +#ifndef COMPONENTS_LUA_YAMLLOADER_H +#define COMPONENTS_LUA_YAMLLOADER_H + +#include +#include +#include +#include + +namespace LuaUtil +{ + + class YamlLoader + { + public: + static sol::object load(const std::string& input, const sol::state_view& lua); + + static sol::object load(std::istream& input, const sol::state_view& lua); + + private: + enum class ScalarType + { + Boolean, + Decimal, + Float, + Hexadecimal, + Infinity, + NotNumber, + Null, + Octal, + String + }; + + static sol::object load(const std::vector rootNodes, const sol::state_view& lua); + + static sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + static sol::table getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + static sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + static ScalarType getScalarType(const YAML::Node& node); + + static sol::object getScalar(const YAML::Node& node, const sol::state_view& lua); + + [[noreturn]] static void nodeError(const YAML::Node& node, const std::string& message); + }; + +} + +#endif // COMPONENTS_LUA_YAMLLOADER_H diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 0b700d46a3..fb354a10a7 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -19,6 +19,7 @@ Lua API reference openmw_animation openmw_async openmw_vfs + openmw_markup openmw_world openmw_self openmw_nearby diff --git a/docs/source/reference/lua-scripting/openmw_markup.rst b/docs/source/reference/lua-scripting/openmw_markup.rst new file mode 100644 index 0000000000..b37afec88f --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_markup.rst @@ -0,0 +1,7 @@ +Package openmw.markup +===================== + +.. include:: version.rst + +.. raw:: html + :file: generated_html/openmw_markup.html diff --git a/docs/source/reference/lua-scripting/tables/packages.rst b/docs/source/reference/lua-scripting/tables/packages.rst index 247bd7eacc..fd82608aed 100644 --- a/docs/source/reference/lua-scripting/tables/packages.rst +++ b/docs/source/reference/lua-scripting/tables/packages.rst @@ -19,6 +19,8 @@ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.vfs ` | everywhere | | Read-only access to data directories via VFS. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.markup ` | everywhere | | API to work with markup languages. | ++------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.world ` | by global scripts | | Read-write access to the game world. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.self ` | by local scripts | | Full access to the object the script is attached to. | diff --git a/files/data/scripts/omw/console/global.lua b/files/data/scripts/omw/console/global.lua index bba0cbc7b3..d1d5ae423a 100644 --- a/files/data/scripts/omw/console/global.lua +++ b/files/data/scripts/omw/console/global.lua @@ -23,6 +23,7 @@ local env = { core = require('openmw.core'), types = require('openmw.types'), vfs = require('openmw.vfs'), + markup = require('openmw.markup'), async = require('openmw.async'), world = require('openmw.world'), aux_util = require('openmw_aux.util'), diff --git a/files/data/scripts/omw/console/local.lua b/files/data/scripts/omw/console/local.lua index 6962b9e798..1acd18df0c 100644 --- a/files/data/scripts/omw/console/local.lua +++ b/files/data/scripts/omw/console/local.lua @@ -25,6 +25,7 @@ local env = { core = require('openmw.core'), types = require('openmw.types'), vfs = require('openmw.vfs'), + markup = require('openmw.markup'), async = require('openmw.async'), nearby = require('openmw.nearby'), self = require('openmw.self'), diff --git a/files/data/scripts/omw/console/menu.lua b/files/data/scripts/omw/console/menu.lua index 9d6dbaf1d7..b6851bc646 100644 --- a/files/data/scripts/omw/console/menu.lua +++ b/files/data/scripts/omw/console/menu.lua @@ -47,6 +47,7 @@ local env = { core = require('openmw.core'), storage = require('openmw.storage'), vfs = require('openmw.vfs'), + markup = require('openmw.markup'), ambient = require('openmw.ambient'), async = require('openmw.async'), ui = require('openmw.ui'), diff --git a/files/data/scripts/omw/console/player.lua b/files/data/scripts/omw/console/player.lua index 6d0ee790a9..9d2e372a93 100644 --- a/files/data/scripts/omw/console/player.lua +++ b/files/data/scripts/omw/console/player.lua @@ -72,6 +72,7 @@ local env = { core = require('openmw.core'), types = require('openmw.types'), vfs = require('openmw.vfs'), + markup = require('openmw.markup'), ambient = require('openmw.ambient'), async = require('openmw.async'), nearby = require('openmw.nearby'), diff --git a/files/lua_api/CMakeLists.txt b/files/lua_api/CMakeLists.txt index 0b960ea259..526ee90955 100644 --- a/files/lua_api/CMakeLists.txt +++ b/files/lua_api/CMakeLists.txt @@ -17,6 +17,7 @@ set(LUA_API_FILES openmw/debug.lua openmw/input.lua openmw/interfaces.lua + openmw/markup.lua openmw/menu.lua openmw/nearby.lua openmw/postprocessing.lua diff --git a/files/lua_api/openmw/markup.lua b/files/lua_api/openmw/markup.lua new file mode 100644 index 0000000000..c8776281d3 --- /dev/null +++ b/files/lua_api/openmw/markup.lua @@ -0,0 +1,37 @@ +--- +-- `openmw.markup` allows to work with markup languages. +-- @module markup +-- @usage local markup = require('openmw.markup') + + + +--- +-- Convert YAML data to Lua object +-- @function [parent=#markup] decodeYaml +-- @param #string inputData Data to decode. It has such limitations: +-- +-- 1. YAML format of [version 1.2](https://yaml.org/spec/1.2.2) is used. +-- 2. Map keys should be scalar values (strings, booleans, numbers). +-- 3. YAML tag system is not supported. +-- 4. If scalar is quoted, it is treated like a string. +-- Othewise type deduction works according to YAML 1.2 [Core Schema](https://yaml.org/spec/1.2.2/#103-core-schema). +-- 5. Circular dependencies between YAML nodes are not allowed. +-- 6. Lua 5.1 does not have integer numbers - all numeric scalars use a #number type (which use a floating point). +-- 7. Integer scalars numbers values are limited by the "int" range. Use floating point notation for larger number in YAML files. +-- @return #any Lua object (can be table or scalar value). +-- @usage local result = markup.decodeYaml('{ "x": 1 }'); +-- -- prints 1 +-- print(result["x"]) + +--- +-- Load YAML file from VFS to Lua object. Conventions are the same as in @{#markup.decodeYaml}. +-- @function [parent=#markup] loadYaml +-- @param #string fileName YAML file path in VFS. +-- @return #any Lua object (can be table or scalar value). +-- @usage -- file contains '{ "x": 1 }' data +-- local result = markup.loadYaml('test.yaml'); +-- -- prints 1 +-- print(result["x"]) + + +return nil From b52f721318a86765fa1e72384f47e8c459f6dae1 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 14 Mar 2024 17:08:23 +0100 Subject: [PATCH 249/451] Use getSubComposite to read AMBI --- components/esm3/loadcell.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index 3c651fac1a..b1efea1aec 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -158,7 +158,7 @@ namespace ESM mWater = waterLevel; break; case fourCC("AMBI"): - esm.getHT(mAmbi.mAmbient, mAmbi.mSunlight, mAmbi.mFog, mAmbi.mFogDensity); + esm.getSubComposite(mAmbi); mHasAmbi = true; break; case fourCC("RGNN"): From 2f4049106533a99beda42de95a22dde3108e471b Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 14 Mar 2024 18:08:18 +0100 Subject: [PATCH 250/451] Fix crash when destroying UI element in the same frame as creating it --- components/lua_ui/element.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 9d45f6ed7f..c239335abb 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -313,9 +313,8 @@ namespace LuaUi { if (mState != Destroyed) { - if (mState != New) + if (mRoot != nullptr) { - assert(mRoot); destroyRoot(mRoot); mRoot = nullptr; } From 68ed77181683da3e417341c8cebc24cab056dde7 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 14 Mar 2024 18:08:38 +0100 Subject: [PATCH 251/451] Fix element detachment logic --- components/lua_ui/element.cpp | 9 --------- components/lua_ui/widget.cpp | 10 +++++++++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index c239335abb..ffd763b40b 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -99,15 +99,6 @@ namespace LuaUi element->create(depth + 1); WidgetExtension* root = element->mRoot; assert(root); - WidgetExtension* parent = root->getParent(); - if (parent) - { - auto children = parent->children(); - std::erase(children, root); - parent->setChildren(children); - root->widget()->detachFromWidget(); - } - root->updateCoord(); return root; } diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index be0ea70387..e61c36c452 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -100,6 +100,8 @@ namespace LuaUi void WidgetExtension::attach(WidgetExtension* ext) { + if (ext->mParent != this) + ext->detachFromParent(); ext->mParent = this; ext->mTemplateChild = false; ext->widget()->attachToWidget(mSlot->widget()); @@ -114,7 +116,13 @@ namespace LuaUi void WidgetExtension::detachFromParent() { - mParent = nullptr; + if (mParent) + { + auto children = mParent->children(); + std::erase(children, this); + mParent->setChildren(children); + mParent = nullptr; + } widget()->detachFromWidget(); } From 28131fd62ba7fef091e2cb67670529bc280af033 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 14 Mar 2024 23:39:19 +0000 Subject: [PATCH 252/451] Fixes for a whole bunch of warnings These warnings were always enabled, but we didn't see them due to https://gitlab.com/OpenMW/openmw/-/issues/7882. I do not fully understand the cause of 7822 as I can't repro it in a minimal CMake project. Some of these fixes are thought through. Some are sensible best guesses. Some are kind of a stab in the dark as I don't know whether there was a possible bug the warning was telling me about that I've done nothing to help by introducing a static_cast. Nearly all of these warnings were about some kind of narrowing conversion, so I'm not sure why they weren't firing with GCC and Clang, which have -Wall -Wextra -pedantic set, which should imply -Wnarrowing, and they can't have been affected by 7882. There were also some warnings being triggered from Boost code. The vast majority of library headers that do questionable things weren't firing warnings off, but for some reason, /external:I wasn't putting these Boost headers into external mode. We need these warnings dealt with one way or another so we can switch the default Windows CI from MSBuild (which doesn't do ccache) to Ninja (which does). I have the necessary magic for that on a branch, but the branch won't build because of these warnings. --- apps/openmw/mwbase/windowmanager.hpp | 2 +- apps/openmw/mwgui/keyboardnavigation.cpp | 4 ++-- apps/openmw/mwgui/messagebox.cpp | 2 +- apps/openmw/mwgui/messagebox.hpp | 2 +- apps/openmw/mwgui/screenfader.cpp | 4 ++-- apps/openmw/mwgui/trainingwindow.cpp | 4 ++-- apps/openmw/mwgui/windowmanagerimp.cpp | 4 ++-- apps/openmw/mwgui/windowmanagerimp.hpp | 2 +- apps/openmw/mwinput/bindingsmanager.cpp | 4 ++-- apps/openmw/mwinput/bindingsmanager.hpp | 4 ++-- apps/openmw/mwsound/ffmpeg_decoder.cpp | 10 ++++++---- apps/openmw/mwsound/ffmpeg_decoder.hpp | 4 ++-- apps/openmw/mwsound/loudness.cpp | 6 +++--- components/bsa/ba2dx10file.cpp | 11 ++++++++--- components/bsa/ba2gnrlfile.cpp | 9 ++++++--- components/bsa/compressedbsafile.cpp | 9 ++++++--- components/debug/debugdraw.cpp | 6 +++--- components/debug/debugging.cpp | 10 +++++++++- components/detournavigator/collisionshapetype.cpp | 2 +- components/files/windowspath.cpp | 2 +- components/fx/lexer.hpp | 2 +- components/l10n/messagebundles.cpp | 14 +++++++------- components/lua/scriptscontainer.cpp | 2 +- components/lua/utf8.cpp | 7 ++++--- components/misc/utf8stream.hpp | 2 +- components/sdlutil/sdlmappings.cpp | 2 +- 26 files changed, 76 insertions(+), 54 deletions(-) diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index c252e0c490..9e8cb05d34 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -290,7 +290,7 @@ namespace MWBase virtual void setEnemy(const MWWorld::Ptr& enemy) = 0; - virtual int getMessagesCount() const = 0; + virtual std::size_t getMessagesCount() const = 0; virtual const Translation::Storage& getTranslationDataStorage() const = 0; diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index 85c7d8ba88..d46a88e580 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -246,12 +246,12 @@ namespace MWGui bool forward = (direction == D_Next || direction == D_Right || direction == D_Down); - int index = found - keyFocusList.begin(); + auto index = found - keyFocusList.begin(); index = forward ? (index + 1) : (index - 1); if (wrap) index = (index + keyFocusList.size()) % keyFocusList.size(); else - index = std::clamp(index, 0, keyFocusList.size() - 1); + index = std::clamp(index, 0, keyFocusList.size() - 1); MyGUI::Widget* next = keyFocusList[index]; int vertdiff = next->getTop() - focus->getTop(); diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index b27adacd0f..1d6e1511c4 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -28,7 +28,7 @@ namespace MWGui MessageBoxManager::clear(); } - int MessageBoxManager::getMessagesCount() + std::size_t MessageBoxManager::getMessagesCount() { return mMessageBoxes.size(); } diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index bb61bd6bd9..feb717e0ad 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -29,7 +29,7 @@ namespace MWGui bool immediate = false, int defaultFocus = -1); bool isInteractiveMessageBox(); - int getMessagesCount(); + std::size_t getMessagesCount(); const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe.get(); } diff --git a/apps/openmw/mwgui/screenfader.cpp b/apps/openmw/mwgui/screenfader.cpp index e22517a360..22c6a803f2 100644 --- a/apps/openmw/mwgui/screenfader.cpp +++ b/apps/openmw/mwgui/screenfader.cpp @@ -97,8 +97,8 @@ namespace MWGui imageBox->setImageTexture(texturePath); const MyGUI::IntSize imageSize = imageBox->getImageSize(); imageBox->setImageCoord( - MyGUI::IntCoord(texCoordOverride.left * imageSize.width, texCoordOverride.top * imageSize.height, - texCoordOverride.width * imageSize.width, texCoordOverride.height * imageSize.height)); + MyGUI::IntCoord(static_cast(texCoordOverride.left * imageSize.width), static_cast(texCoordOverride.top * imageSize.height), + static_cast(texCoordOverride.width * imageSize.width), static_cast(texCoordOverride.height * imageSize.height))); } } diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index fa4fd266b5..890aa0ba68 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -189,8 +189,8 @@ namespace MWGui mProgressBar.setProgress(0, 2); mTimeAdvancer.run(2); - MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.2); - MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.2, false, 0.2); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.2f); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.2f, false, 0.2f); } void TrainingWindow::onTrainingProgressChanged(int cur, int total) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 29b9cb0e84..4678a269e1 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1685,9 +1685,9 @@ namespace MWGui mHud->setEnemy(enemy); } - int WindowManager::getMessagesCount() const + std::size_t WindowManager::getMessagesCount() const { - int count = 0; + std::size_t count = 0; if (mMessageBoxManager) count = mMessageBoxManager->getMessagesCount(); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 3445ebdb9a..617570b336 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -313,7 +313,7 @@ namespace MWGui void setEnemy(const MWWorld::Ptr& enemy) override; - int getMessagesCount() const override; + std::size_t getMessagesCount() const override; const Translation::Storage& getTranslationDataStorage() const override; diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 3f505896f4..a6bab19673 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -627,12 +627,12 @@ namespace MWInput return mInputBinder->detectingBindingState(); } - void BindingsManager::mousePressed(const SDL_MouseButtonEvent& arg, int deviceID) + void BindingsManager::mousePressed(const SDL_MouseButtonEvent& arg, Uint8 deviceID) { mInputBinder->mousePressed(arg, deviceID); } - void BindingsManager::mouseReleased(const SDL_MouseButtonEvent& arg, int deviceID) + void BindingsManager::mouseReleased(const SDL_MouseButtonEvent& arg, Uint8 deviceID) { mInputBinder->mouseReleased(arg, deviceID); } diff --git a/apps/openmw/mwinput/bindingsmanager.hpp b/apps/openmw/mwinput/bindingsmanager.hpp index a11baf74de..bee9e07cf7 100644 --- a/apps/openmw/mwinput/bindingsmanager.hpp +++ b/apps/openmw/mwinput/bindingsmanager.hpp @@ -47,8 +47,8 @@ namespace MWInput SDL_GameController* getControllerOrNull() const; - void mousePressed(const SDL_MouseButtonEvent& evt, int deviceID); - void mouseReleased(const SDL_MouseButtonEvent& arg, int deviceID); + void mousePressed(const SDL_MouseButtonEvent& evt, Uint8 deviceID); + void mouseReleased(const SDL_MouseButtonEvent& arg, Uint8 deviceID); void mouseMoved(const SDLUtil::MouseMotionEvent& arg); void mouseWheelMoved(const SDL_MouseWheelEvent& arg); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index bd63d3de40..a6f3d0336f 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -21,7 +21,9 @@ namespace MWSound std::streamsize count = stream.gcount(); if (count == 0) return AVERROR_EOF; - return count; + if (count > std::numeric_limits::max()) + return AVERROR_BUG; + return static_cast(count); } catch (std::exception&) { @@ -72,7 +74,7 @@ namespace MWSound if (!mStream) return false; - int stream_idx = mStream - mFormatCtx->streams; + std::ptrdiff_t stream_idx = mStream - mFormatCtx->streams; while (av_read_frame(mFormatCtx, &mPacket) >= 0) { /* Check if the packet belongs to this stream */ @@ -427,9 +429,9 @@ namespace MWSound size_t FFmpeg_Decoder::getSampleOffset() { - int delay = (mFrameSize - mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) + std::size_t delay = (mFrameSize - mFramePos) / av_get_channel_layout_nb_channels(mOutputChannelLayout) / av_get_bytes_per_sample(mOutputSampleFormat); - return (int)(mNextPts * mCodecCtx->sample_rate) - delay; + return static_cast(mNextPts * mCodecCtx->sample_rate) - delay; } FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 88dd3316f5..9d15888fcf 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -41,8 +41,8 @@ namespace MWSound AVPacket mPacket; AVFrame* mFrame; - int mFrameSize; - int mFramePos; + std::size_t mFrameSize; + std::size_t mFramePos; double mNextPts; diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp index b1c1a3f2af..c99ef15e9f 100644 --- a/apps/openmw/mwsound/loudness.cpp +++ b/apps/openmw/mwsound/loudness.cpp @@ -15,8 +15,8 @@ namespace MWSound return; int samplesPerSegment = static_cast(mSampleRate / mSamplesPerSec); - int numSamples = bytesToFrames(mQueue.size(), mChannelConfig, mSampleType); - int advance = framesToBytes(1, mChannelConfig, mSampleType); + std::size_t numSamples = bytesToFrames(mQueue.size(), mChannelConfig, mSampleType); + std::size_t advance = framesToBytes(1, mChannelConfig, mSampleType); int segment = 0; int sample = 0; @@ -61,7 +61,7 @@ namespace MWSound if (mSamplesPerSec <= 0.0f || mSamples.empty() || sec < 0.0f) return 0.0f; - size_t index = std::clamp(sec * mSamplesPerSec, 0, mSamples.size() - 1); + size_t index = std::clamp(static_cast(sec * mSamplesPerSec), 0, mSamples.size() - 1); return mSamples[index]; } diff --git a/components/bsa/ba2dx10file.cpp b/components/bsa/ba2dx10file.cpp index aa3f8d0581..946a68fcd5 100644 --- a/components/bsa/ba2dx10file.cpp +++ b/components/bsa/ba2dx10file.cpp @@ -6,16 +6,21 @@ #include -#include -#include -#include #if defined(_MSC_VER) +// why is this necessary? These are included with /external:I #pragma warning(push) #pragma warning(disable : 4706) +#pragma warning(disable : 4702) +#include +#include +#include #include #pragma warning(pop) #else +#include +#include +#include #include #endif diff --git a/components/bsa/ba2gnrlfile.cpp b/components/bsa/ba2gnrlfile.cpp index 02df12593c..436f5cc4bc 100644 --- a/components/bsa/ba2gnrlfile.cpp +++ b/components/bsa/ba2gnrlfile.cpp @@ -6,15 +6,18 @@ #include -#include -#include - #if defined(_MSC_VER) +// why is this necessary? These are included with /external:I #pragma warning(push) #pragma warning(disable : 4706) +#pragma warning(disable : 4702) +#include +#include #include #pragma warning(pop) #else +#include +#include #include #endif diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index 99efe7a587..ea39b42540 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -30,15 +30,18 @@ #include -#include -#include #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable : 4706) +#pragma warning(disable : 4702) +#include +#include #include #pragma warning(pop) #else +#include +#include #include #endif @@ -168,7 +171,7 @@ namespace Bsa name.resize(input.gcount()); if (name.back() != '\0') fail("Failed to read a filename: filename is too long"); - mHeader.mFileNamesLength -= input.gcount(); + mHeader.mFileNamesLength -= static_cast(input.gcount()); file.mName.insert(file.mName.begin(), folder.mName.begin(), folder.mName.end()); file.mName.insert(file.mName.begin() + folder.mName.size(), '\\'); } diff --git a/components/debug/debugdraw.cpp b/components/debug/debugdraw.cpp index 2bc7358259..cd98fe2b6d 100644 --- a/components/debug/debugdraw.cpp +++ b/components/debug/debugdraw.cpp @@ -124,7 +124,7 @@ static void generateCylinder(osg::Geometry& geom, float radius, float height, in for (int i = 0; i < subdiv; i++) { float theta = (float(i) / float(subdiv)) * osg::PI * 2.; - osg::Vec3 pos = sphereCoordToCartesian(theta, osg::PI_2, 1.); + osg::Vec3 pos = sphereCoordToCartesian(theta, osg::PI_2f, 1.); pos *= radius; pos.z() = height / 2.; vertices->push_back(pos); @@ -150,7 +150,7 @@ static void generateCylinder(osg::Geometry& geom, float radius, float height, in for (int i = 0; i < subdiv; i++) { float theta = float(i) / float(subdiv) * osg::PI * 2.; - osg::Vec3 pos = sphereCoordToCartesian(theta, osg::PI_2, 1.); + osg::Vec3 pos = sphereCoordToCartesian(theta, osg::PI_2f, 1.); pos *= radius; pos.z() = -height / 2.; vertices->push_back(pos); @@ -162,7 +162,7 @@ static void generateCylinder(osg::Geometry& geom, float radius, float height, in for (int i = 0; i < subdiv; i++) { float theta = float(i) / float(subdiv) * osg::PI * 2.; - osg::Vec3 normal = sphereCoordToCartesian(theta, osg::PI_2, 1.); + osg::Vec3 normal = sphereCoordToCartesian(theta, osg::PI_2f, 1.); auto posTop = normal; posTop *= radius; auto posBot = posTop; diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index d170cf1929..e9e50ff836 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -6,7 +6,15 @@ #include #include +#ifdef _MSC_VER +// TODO: why is this necessary? this has /external:I +#pragma warning(push) +#pragma warning(disable : 4702) +#endif #include +#ifdef _MSC_VER +#pragma warning(pop) +#endif #include #include @@ -111,7 +119,7 @@ namespace Debug msg = msg.substr(1); char prefix[32]; - int prefixSize; + std::size_t prefixSize; { prefix[0] = '['; const auto now = std::chrono::system_clock::now(); diff --git a/components/detournavigator/collisionshapetype.cpp b/components/detournavigator/collisionshapetype.cpp index b20ae6147f..b68d5cd239 100644 --- a/components/detournavigator/collisionshapetype.cpp +++ b/components/detournavigator/collisionshapetype.cpp @@ -15,7 +15,7 @@ namespace DetourNavigator return static_cast(value); } std::string error("Invalid CollisionShapeType value: \""); - error += value; + error += std::to_string(value); error += '"'; throw std::invalid_argument(error); } diff --git a/components/files/windowspath.cpp b/components/files/windowspath.cpp index 9be3d13a46..bbe0325b58 100644 --- a/components/files/windowspath.cpp +++ b/components/files/windowspath.cpp @@ -101,7 +101,7 @@ namespace Files { // Key existed, let's try to read the install dir std::array buf{}; - DWORD len = buf.size() * sizeof(wchar_t); + DWORD len = static_cast(buf.size() * sizeof(wchar_t)); if (RegQueryValueExW(hKey, L"Installed Path", nullptr, nullptr, reinterpret_cast(buf.data()), &len) == ERROR_SUCCESS) diff --git a/components/fx/lexer.hpp b/components/fx/lexer.hpp index 01b3a3a56a..fc7d4ec9d7 100644 --- a/components/fx/lexer.hpp +++ b/components/fx/lexer.hpp @@ -30,7 +30,7 @@ namespace fx public: struct Block { - int line; + std::size_t line; std::string_view content; }; diff --git a/components/l10n/messagebundles.cpp b/components/l10n/messagebundles.cpp index 4656116487..9a3dc5e00f 100644 --- a/components/l10n/messagebundles.cpp +++ b/components/l10n/messagebundles.cpp @@ -77,7 +77,7 @@ namespace l10n { const auto key = it.first.as(); const auto value = it.second.as(); - icu::UnicodeString pattern = icu::UnicodeString::fromUTF8(icu::StringPiece(value.data(), value.size())); + icu::UnicodeString pattern = icu::UnicodeString::fromUTF8(icu::StringPiece(value.data(), static_cast(value.size()))); icu::ErrorCode status; UParseError parseError; icu::MessageFormat message(pattern, langOrEn, parseError, status); @@ -115,7 +115,7 @@ namespace l10n std::vector argValues; for (auto& [k, v] : args) { - argNames.push_back(icu::UnicodeString::fromUTF8(icu::StringPiece(k.data(), k.size()))); + argNames.push_back(icu::UnicodeString::fromUTF8(icu::StringPiece(k.data(), static_cast(k.size())))); argValues.push_back(v); } return formatMessage(key, argNames, argValues); @@ -160,9 +160,9 @@ namespace l10n if (message) { if (!args.empty() && !argNames.empty()) - message->format(argNames.data(), args.data(), args.size(), result, success); + message->format(argNames.data(), args.data(), static_cast(args.size()), result, success); else - message->format(nullptr, nullptr, args.size(), result, success); + message->format(nullptr, nullptr, static_cast(args.size()), result, success); checkSuccess(success, std::string("Failed to format message ") + key.data()); result.toUTF8String(resultString); return resultString; @@ -174,15 +174,15 @@ namespace l10n } UParseError parseError; icu::MessageFormat defaultMessage( - icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), key.size())), defaultLocale, parseError, success); + icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), static_cast(key.size()))), defaultLocale, parseError, success); if (!checkSuccess(success, std::string("Failed to create message ") + key.data(), parseError)) // If we can't parse the key as a pattern, just return the key return std::string(key); if (!args.empty() && !argNames.empty()) - defaultMessage.format(argNames.data(), args.data(), args.size(), result, success); + defaultMessage.format(argNames.data(), args.data(), static_cast(args.size()), result, success); else - defaultMessage.format(nullptr, nullptr, args.size(), result, success); + defaultMessage.format(nullptr, nullptr, static_cast(args.size()), result, success); checkSuccess(success, std::string("Failed to format message ") + key.data()); result.toUTF8String(resultString); return resultString; diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 9b4a119ba4..ff45b963ca 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -590,7 +590,7 @@ namespace LuaUtil updateTimerQueue(mGameTimersQueue, gameTime); } - static constexpr float instructionCountAvgCoef = 1.0 / 30; // averaging over approximately 30 frames + static constexpr float instructionCountAvgCoef = 1.0f / 30; // averaging over approximately 30 frames void ScriptsContainer::statsNextFrame() { diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp index b486766b6a..2a585dac2d 100644 --- a/components/lua/utf8.cpp +++ b/components/lua/utf8.cpp @@ -14,7 +14,7 @@ namespace return (arg.get_type() == sol::type::lua_nil || arg.get_type() == sol::type::none); } - inline double getInteger(const sol::stack_proxy arg, const size_t n, std::string_view name) + inline std::int64_t getInteger(const sol::stack_proxy arg, const size_t n, std::string_view name) { double integer; if (!arg.is()) @@ -25,7 +25,7 @@ namespace throw std::runtime_error( Misc::StringUtils::format("bad argument #%i to '%s' (number has no integer representation)", n, name)); - return integer; + return static_cast(integer); } // If the input 'pos' is negative, it is treated as counting from the end of the string, @@ -104,7 +104,8 @@ namespace LuaUtf8 throw std::runtime_error( "bad argument #" + std::to_string(i + 1) + " to 'char' (value out of range)"); - result += converter.to_bytes(codepoint); + // this feels dodgy if wchar_t is 16-bit as MAXUTF won't fit in sixteen bits + result += converter.to_bytes(static_cast(codepoint)); } return result; }; diff --git a/components/misc/utf8stream.hpp b/components/misc/utf8stream.hpp index 271376834d..5eb5f99b84 100644 --- a/components/misc/utf8stream.hpp +++ b/components/misc/utf8stream.hpp @@ -75,7 +75,7 @@ public: return std::make_pair(chr, cur); } - int octets; + std::size_t octets; UnicodeChar chr; std::tie(octets, chr) = getOctetCount(*cur++); diff --git a/components/sdlutil/sdlmappings.cpp b/components/sdlutil/sdlmappings.cpp index fe248e6f70..8a82206c33 100644 --- a/components/sdlutil/sdlmappings.cpp +++ b/components/sdlutil/sdlmappings.cpp @@ -83,7 +83,7 @@ namespace SDLUtil Uint8 myGuiMouseButtonToSdl(MyGUI::MouseButton button) { - Uint8 value = button.getValue() + 1; + Uint8 value = static_cast(button.getValue() + 1); if (value == SDL_BUTTON_RIGHT) value = SDL_BUTTON_MIDDLE; else if (value == SDL_BUTTON_MIDDLE) From ff3ffa13b6d4a40dd95cc8a4d77eec5c6cd6775b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 14 Mar 2024 23:54:22 +0000 Subject: [PATCH 253/451] Auto format --- apps/openmw/mwgui/screenfader.cpp | 7 ++++--- apps/openmw/mwinput/bindingsmanager.cpp | 5 +---- components/bsa/ba2dx10file.cpp | 5 ++--- components/bsa/ba2gnrlfile.cpp | 4 ++-- components/bsa/compressedbsafile.cpp | 5 ++--- components/debug/debugging.cpp | 5 +---- components/l10n/messagebundles.cpp | 12 ++++++++---- 7 files changed, 20 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwgui/screenfader.cpp b/apps/openmw/mwgui/screenfader.cpp index 22c6a803f2..0068ba7960 100644 --- a/apps/openmw/mwgui/screenfader.cpp +++ b/apps/openmw/mwgui/screenfader.cpp @@ -96,9 +96,10 @@ namespace MWGui { imageBox->setImageTexture(texturePath); const MyGUI::IntSize imageSize = imageBox->getImageSize(); - imageBox->setImageCoord( - MyGUI::IntCoord(static_cast(texCoordOverride.left * imageSize.width), static_cast(texCoordOverride.top * imageSize.height), - static_cast(texCoordOverride.width * imageSize.width), static_cast(texCoordOverride.height * imageSize.height))); + imageBox->setImageCoord(MyGUI::IntCoord(static_cast(texCoordOverride.left * imageSize.width), + static_cast(texCoordOverride.top * imageSize.height), + static_cast(texCoordOverride.width * imageSize.width), + static_cast(texCoordOverride.height * imageSize.height))); } } diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index a6bab19673..67e71bd0d0 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -170,10 +170,7 @@ namespace MWInput MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } - void setDetectingKeyboard(bool detecting) - { - mDetectingKeyboard = detecting; - } + void setDetectingKeyboard(bool detecting) { mDetectingKeyboard = detecting; } private: ICS::InputControlSystem* mInputBinder; diff --git a/components/bsa/ba2dx10file.cpp b/components/bsa/ba2dx10file.cpp index 946a68fcd5..82a5ee8473 100644 --- a/components/bsa/ba2dx10file.cpp +++ b/components/bsa/ba2dx10file.cpp @@ -6,22 +6,21 @@ #include - #if defined(_MSC_VER) // why is this necessary? These are included with /external:I #pragma warning(push) #pragma warning(disable : 4706) #pragma warning(disable : 4702) #include +#include #include #include -#include #pragma warning(pop) #else #include +#include #include #include -#include #endif #include diff --git a/components/bsa/ba2gnrlfile.cpp b/components/bsa/ba2gnrlfile.cpp index 436f5cc4bc..da5ad47029 100644 --- a/components/bsa/ba2gnrlfile.cpp +++ b/components/bsa/ba2gnrlfile.cpp @@ -12,13 +12,13 @@ #pragma warning(disable : 4706) #pragma warning(disable : 4702) #include -#include #include +#include #pragma warning(pop) #else #include -#include #include +#include #endif #include diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index ea39b42540..14d90f5d91 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -30,19 +30,18 @@ #include - #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable : 4706) #pragma warning(disable : 4702) #include -#include #include +#include #pragma warning(pop) #else #include -#include #include +#include #endif #include diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index e9e50ff836..2d43886cab 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -190,10 +190,7 @@ namespace Debug CurrentDebugLevel = Verbose; } - virtual std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) - { - return size; - } + virtual std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) { return size; } }; #if defined _WIN32 && defined _DEBUG diff --git a/components/l10n/messagebundles.cpp b/components/l10n/messagebundles.cpp index 9a3dc5e00f..a46b05c6f4 100644 --- a/components/l10n/messagebundles.cpp +++ b/components/l10n/messagebundles.cpp @@ -77,7 +77,8 @@ namespace l10n { const auto key = it.first.as(); const auto value = it.second.as(); - icu::UnicodeString pattern = icu::UnicodeString::fromUTF8(icu::StringPiece(value.data(), static_cast(value.size()))); + icu::UnicodeString pattern = icu::UnicodeString::fromUTF8( + icu::StringPiece(value.data(), static_cast(value.size()))); icu::ErrorCode status; UParseError parseError; icu::MessageFormat message(pattern, langOrEn, parseError, status); @@ -115,7 +116,8 @@ namespace l10n std::vector argValues; for (auto& [k, v] : args) { - argNames.push_back(icu::UnicodeString::fromUTF8(icu::StringPiece(k.data(), static_cast(k.size())))); + argNames.push_back( + icu::UnicodeString::fromUTF8(icu::StringPiece(k.data(), static_cast(k.size())))); argValues.push_back(v); } return formatMessage(key, argNames, argValues); @@ -174,13 +176,15 @@ namespace l10n } UParseError parseError; icu::MessageFormat defaultMessage( - icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), static_cast(key.size()))), defaultLocale, parseError, success); + icu::UnicodeString::fromUTF8(icu::StringPiece(key.data(), static_cast(key.size()))), + defaultLocale, parseError, success); if (!checkSuccess(success, std::string("Failed to create message ") + key.data(), parseError)) // If we can't parse the key as a pattern, just return the key return std::string(key); if (!args.empty() && !argNames.empty()) - defaultMessage.format(argNames.data(), args.data(), static_cast(args.size()), result, success); + defaultMessage.format( + argNames.data(), args.data(), static_cast(args.size()), result, success); else defaultMessage.format(nullptr, nullptr, static_cast(args.size()), result, success); checkSuccess(success, std::string("Failed to format message ") + key.data()); From 9638fbabb47467902deec226e448d5e16cdcac83 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 15 Mar 2024 00:11:19 +0000 Subject: [PATCH 254/451] https://www.youtube.com/watch?v=2_6U9gkQeqY --- apps/openmw/mwinput/bindingsmanager.cpp | 5 ++++- components/debug/debugging.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 67e71bd0d0..a6bab19673 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -170,7 +170,10 @@ namespace MWInput MWBase::Environment::get().getWindowManager()->notifyInputActionBound(); } - void setDetectingKeyboard(bool detecting) { mDetectingKeyboard = detecting; } + void setDetectingKeyboard(bool detecting) + { + mDetectingKeyboard = detecting; + } private: ICS::InputControlSystem* mInputBinder; diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 2d43886cab..e9e50ff836 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -190,7 +190,10 @@ namespace Debug CurrentDebugLevel = Verbose; } - virtual std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) { return size; } + virtual std::streamsize writeImpl(const char* str, std::streamsize size, Level debugLevel) + { + return size; + } }; #if defined _WIN32 && defined _DEBUG From a06ab94a209e1123ec8c37eedc51c021f1ee4756 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 15 Mar 2024 00:42:15 +0000 Subject: [PATCH 255/451] Canonicalise resolved representation of data directories --- components/config/gamesettings.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 976d5e20f2..21110562d5 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -35,14 +35,20 @@ void Config::GameSettings::validatePaths() for (const auto& dataDir : paths) { if (QDir(dataDir.value).exists()) - mDataDirs.append(dataDir); + { + SettingValue copy = dataDir; + copy.value = QDir(dataDir.value).canonicalPath(); + mDataDirs.append(copy); + } } // Do the same for data-local const QString& local = mSettings.value(QString("data-local")).value; if (!local.isEmpty() && QDir(local).exists()) - mDataLocal = local; + { + mDataLocal = QDir(local).canonicalPath(); + } } QString Config::GameSettings::getResourcesVfs() const From dd18e17c97d023669d06150634516a8a18b33cb6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 15 Mar 2024 00:47:01 +0000 Subject: [PATCH 256/451] And now Clang's noticed questionable type conversions --- apps/openmw/mwsound/loudness.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp index c99ef15e9f..440207910e 100644 --- a/apps/openmw/mwsound/loudness.cpp +++ b/apps/openmw/mwsound/loudness.cpp @@ -18,8 +18,8 @@ namespace MWSound std::size_t numSamples = bytesToFrames(mQueue.size(), mChannelConfig, mSampleType); std::size_t advance = framesToBytes(1, mChannelConfig, mSampleType); - int segment = 0; - int sample = 0; + std::size_t segment = 0; + std::size_t sample = 0; while (segment < numSamples / samplesPerSegment) { float sum = 0; From b5f61a119a01bd24e9d5f74ccaeeef96ccf4d6b6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 15 Mar 2024 13:42:28 +0000 Subject: [PATCH 257/451] min --- apps/openmw/mwsound/loudness.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwsound/loudness.cpp b/apps/openmw/mwsound/loudness.cpp index 440207910e..2a6ac5ac8e 100644 --- a/apps/openmw/mwsound/loudness.cpp +++ b/apps/openmw/mwsound/loudness.cpp @@ -61,7 +61,7 @@ namespace MWSound if (mSamplesPerSec <= 0.0f || mSamples.empty() || sec < 0.0f) return 0.0f; - size_t index = std::clamp(static_cast(sec * mSamplesPerSec), 0, mSamples.size() - 1); + size_t index = std::min(static_cast(sec * mSamplesPerSec), mSamples.size() - 1); return mSamples[index]; } From 009ccca978c2fbac73cfd5bc3f5d667603c60fcb Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 15 Mar 2024 18:19:44 +0400 Subject: [PATCH 258/451] Modify sound API permissions --- CMakeLists.txt | 2 +- apps/openmw/mwlua/soundbindings.cpp | 104 ++++++++++++++++++---------- files/lua_api/openmw/ambient.lua | 22 ++++++ files/lua_api/openmw/core.lua | 42 ++++++----- 4 files changed, 110 insertions(+), 60 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c4abe65498..5263d849e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,7 +81,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 57) +set(OPENMW_LUA_API_REVISION 58) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index ad4a498153..2023f2b341 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -11,6 +11,7 @@ #include #include "luamanagerimp.hpp" +#include "objectvariant.hpp" namespace { @@ -28,6 +29,27 @@ namespace float mFade = 1.f; }; + MWWorld::Ptr getMutablePtrOrThrow(const MWLua::ObjectVariant& variant) + { + if (variant.isLObject()) + throw std::runtime_error("Local scripts can only modify object they are attached to."); + + MWWorld::Ptr ptr = variant.ptr(); + if (ptr.isEmpty()) + throw std::runtime_error("Invalid object"); + + return ptr; + } + + MWWorld::Ptr getPtrOrThrow(const MWLua::ObjectVariant& variant) + { + MWWorld::Ptr ptr = variant.ptr(); + if (ptr.isEmpty()) + throw std::runtime_error("Invalid object"); + + return ptr; + } + PlaySoundArgs getPlaySoundArgs(const sol::optional& options) { PlaySoundArgs args; @@ -121,6 +143,17 @@ namespace MWLua sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted, args.mFade); }; + api["say"] + = [luaManager = context.mLuaManager](std::string_view fileName, sol::optional text) { + MWBase::Environment::get().getSoundManager()->say(VFS::Path::Normalized(fileName)); + if (text) + luaManager->addUIMessage(*text); + }; + + api["stopSay"] = []() { MWBase::Environment::get().getSoundManager()->stopSay(MWWorld::ConstPtr()); }; + api["isSayActive"] + = []() { return MWBase::Environment::get().getSoundManager()->sayActive(MWWorld::ConstPtr()); }; + api["isMusicPlaying"] = []() { return MWBase::Environment::get().getSoundManager()->isMusicPlaying(); }; api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); }; @@ -137,64 +170,61 @@ namespace MWLua api["isEnabled"] = []() { return MWBase::Environment::get().getSoundManager()->isEnabled(); }; api["playSound3d"] - = [](std::string_view soundId, const Object& object, const sol::optional& options) { + = [](std::string_view soundId, const sol::object& object, const sol::optional& options) { auto args = getPlaySoundArgs(options); auto playMode = getPlayMode(args, true); ESM::RefId sound = ESM::RefId::deserializeText(soundId); + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); MWBase::Environment::get().getSoundManager()->playSound3D( - object.ptr(), sound, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); + ptr, sound, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); }; api["playSoundFile3d"] - = [](std::string_view fileName, const Object& object, const sol::optional& options) { + = [](std::string_view fileName, const sol::object& object, const sol::optional& options) { auto args = getPlaySoundArgs(options); auto playMode = getPlayMode(args, true); + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); - MWBase::Environment::get().getSoundManager()->playSound3D(object.ptr(), fileName, args.mVolume, - args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); + MWBase::Environment::get().getSoundManager()->playSound3D( + ptr, fileName, args.mVolume, args.mPitch, MWSound::Type::Sfx, playMode, args.mTimeOffset); }; - api["stopSound3d"] = [](std::string_view soundId, const Object& object) { + api["stopSound3d"] = [](std::string_view soundId, const sol::object& object) { ESM::RefId sound = ESM::RefId::deserializeText(soundId); - MWBase::Environment::get().getSoundManager()->stopSound3D(object.ptr(), sound); + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->stopSound3D(ptr, sound); }; - api["stopSoundFile3d"] = [](std::string_view fileName, const Object& object) { - MWBase::Environment::get().getSoundManager()->stopSound3D(object.ptr(), fileName); + api["stopSoundFile3d"] = [](std::string_view fileName, const sol::object& object) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->stopSound3D(ptr, fileName); }; - api["isSoundPlaying"] = [](std::string_view soundId, const Object& object) { + api["isSoundPlaying"] = [](std::string_view soundId, const sol::object& object) { ESM::RefId sound = ESM::RefId::deserializeText(soundId); - return MWBase::Environment::get().getSoundManager()->getSoundPlaying(object.ptr(), sound); + const MWWorld::Ptr& ptr = getPtrOrThrow(ObjectVariant(object)); + return MWBase::Environment::get().getSoundManager()->getSoundPlaying(ptr, sound); }; - api["isSoundFilePlaying"] = [](std::string_view fileName, const Object& object) { - return MWBase::Environment::get().getSoundManager()->getSoundPlaying(object.ptr(), fileName); + api["isSoundFilePlaying"] = [](std::string_view fileName, const sol::object& object) { + const MWWorld::Ptr& ptr = getPtrOrThrow(ObjectVariant(object)); + return MWBase::Environment::get().getSoundManager()->getSoundPlaying(ptr, fileName); }; - api["say"] = sol::overload( - [luaManager = context.mLuaManager]( - std::string_view fileName, const Object& object, sol::optional text) { - MWBase::Environment::get().getSoundManager()->say(object.ptr(), VFS::Path::Normalized(fileName)); - if (text) - luaManager->addUIMessage(*text); - }, - [luaManager = context.mLuaManager](std::string_view fileName, sol::optional text) { - MWBase::Environment::get().getSoundManager()->say(VFS::Path::Normalized(fileName)); - if (text) - luaManager->addUIMessage(*text); - }); - api["stopSay"] = sol::overload( - [](const Object& object) { - const MWWorld::Ptr& objPtr = object.ptr(); - MWBase::Environment::get().getSoundManager()->stopSay(objPtr); - }, - []() { MWBase::Environment::get().getSoundManager()->stopSay(MWWorld::ConstPtr()); }); - api["isSayActive"] = sol::overload( - [](const Object& object) { - const MWWorld::Ptr& objPtr = object.ptr(); - return MWBase::Environment::get().getSoundManager()->sayActive(objPtr); - }, - []() { return MWBase::Environment::get().getSoundManager()->sayActive(MWWorld::ConstPtr()); }); + api["say"] = [luaManager = context.mLuaManager]( + std::string_view fileName, const sol::object& object, sol::optional text) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->say(ptr, VFS::Path::Normalized(fileName)); + if (text) + luaManager->addUIMessage(*text); + }; + api["stopSay"] = [](const sol::object& object) { + MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + MWBase::Environment::get().getSoundManager()->stopSay(ptr); + }; + api["isSayActive"] = [](const sol::object& object) { + const MWWorld::Ptr& ptr = getPtrOrThrow(ObjectVariant(object)); + return MWBase::Environment::get().getSoundManager()->sayActive(ptr); + }; using SoundStore = MWWorld::Store; sol::usertype soundStoreT = lua.new_usertype("ESM3_SoundStore"); diff --git a/files/lua_api/openmw/ambient.lua b/files/lua_api/openmw/ambient.lua index c10e50ff4a..ff776f84fb 100644 --- a/files/lua_api/openmw/ambient.lua +++ b/files/lua_api/openmw/ambient.lua @@ -95,4 +95,26 @@ -- @return #boolean -- @usage local isPlaying = ambient.isMusicPlaying(); +--- +-- Play an ambient voiceover. +-- @function [parent=#ambient] say +-- @param #string fileName Path to sound file in VFS +-- @param #string text Subtitle text (optional) +-- @usage -- play voiceover and print messagebox +-- ambient.say("Sound\\Vo\\Misc\\voice.mp3", "Subtitle text") +-- @usage -- play voiceover, without messagebox +-- ambient.say("Sound\\Vo\\Misc\\voice.mp3") + +--- +-- Stop an ambient voiceover +-- @function [parent=#ambient] stopSay +-- @param #string fileName Path to sound file in VFS +-- @usage ambient.stopSay(); + +--- +-- Check if an ambient voiceover is playing +-- @function [parent=#Sound] isSayActive +-- @return #boolean +-- @usage local isActive = isSayActive(); + return nil diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 66fb817362..46978ce903 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -714,6 +714,8 @@ --- -- Play a 3D sound, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] playSound3d -- @param #string soundId ID of Sound record to play -- @param #GameObject object Object to which we attach the sound @@ -733,6 +735,8 @@ --- -- Play a 3D sound file, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] playSoundFile3d -- @param #string fileName Path to sound file in VFS -- @param #GameObject object Object to which we attach the sound @@ -752,6 +756,8 @@ --- -- Stop a 3D sound, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] stopSound3d -- @param #string soundId ID of Sound record to stop -- @param #GameObject object Object on which we want to stop sound @@ -759,6 +765,8 @@ --- -- Stop a 3D sound file, attached to object +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] stopSoundFile3d -- @param #string fileName Path to sound file in VFS -- @param #GameObject object Object on which we want to stop sound @@ -781,42 +789,32 @@ -- @usage local isPlaying = core.sound.isSoundFilePlaying("Sound\\test.mp3", object); --- --- Play an animated voiceover. Has two overloads: --- --- * With an "object" argument: play sound for given object, with speaking animation if possible --- * Without an "object" argument: play sound globally, without object +-- Play an animated voiceover. +-- In local scripts can be used only on self. -- @function [parent=#Sound] say -- @param #string fileName Path to sound file in VFS --- @param #GameObject object Object on which we want to play an animated voiceover (optional) +-- @param #GameObject object Object on which we want to play an animated voiceover -- @param #string text Subtitle text (optional) -- @usage -- play voiceover for object and print messagebox -- core.sound.say("Sound\\Vo\\Misc\\voice.mp3", object, "Subtitle text") --- @usage -- play voiceover globally and print messagebox --- core.sound.say("Sound\\Vo\\Misc\\voice.mp3", "Subtitle text") --- @usage -- play voiceover for object without messagebox +-- @usage -- play voiceover for object, without messagebox -- core.sound.say("Sound\\Vo\\Misc\\voice.mp3", object) --- @usage -- play voiceover globally without messagebox --- core.sound.say("Sound\\Vo\\Misc\\voice.mp3") --- --- Stop animated voiceover +-- Stop an animated voiceover +-- +-- In local scripts can be used only on self. -- @function [parent=#Sound] stopSay -- @param #string fileName Path to sound file in VFS --- @param #GameObject object Object on which we want to stop an animated voiceover (optional) --- @usage -- stop voice for given object --- core.sound.stopSay(object); --- @usage -- stop global voice --- core.sound.stopSay(); +-- @param #GameObject object Object on which we want to stop an animated voiceover +-- @usage core.sound.stopSay(object); --- --- Check if animated voiceover is playing +-- Check if an animated voiceover is playing -- @function [parent=#Sound] isSayActive --- @param #GameObject object Object on which we want to check an animated voiceover (optional) +-- @param #GameObject object Object on which we want to check an animated voiceover -- @return #boolean --- @usage -- check voice for given object --- local isActive = isSayActive(object); --- @usage -- check global voice --- local isActive = isSayActive(); +-- @usage local isActive = isSayActive(object); --- -- @type SoundRecord From 6da151cf771495bf91ab6aa9406bc3aa35f8cb92 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 15 Mar 2024 20:12:47 +0400 Subject: [PATCH 259/451] Fix GCC build --- components/detournavigator/recastmesh.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/detournavigator/recastmesh.hpp b/components/detournavigator/recastmesh.hpp index ac487d3b68..6d06db0799 100644 --- a/components/detournavigator/recastmesh.hpp +++ b/components/detournavigator/recastmesh.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include From ddb2c15bc9d5f9351edccc66e942541f7578e19d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 15 Mar 2024 16:31:02 +0000 Subject: [PATCH 260/451] Review --- apps/openmw/mwgui/keyboardnavigation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index d46a88e580..9d4971951a 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -246,12 +246,12 @@ namespace MWGui bool forward = (direction == D_Next || direction == D_Right || direction == D_Down); - auto index = found - keyFocusList.begin(); + std::ptrdiff_t index{ found - keyFocusList.begin() }; index = forward ? (index + 1) : (index - 1); if (wrap) index = (index + keyFocusList.size()) % keyFocusList.size(); else - index = std::clamp(index, 0, keyFocusList.size() - 1); + index = std::clamp(index, 0, keyFocusList.size() - 1); MyGUI::Widget* next = keyFocusList[index]; int vertdiff = next->getTop() - focus->getTop(); From 2b53c2335f57bf0d9b5deab7d919a975fb137cb4 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 15 Mar 2024 21:26:03 +0100 Subject: [PATCH 261/451] Support printing stats table in json format --- scripts/osg_stats.py | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 3cdd0febae..d898accb10 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -7,6 +7,7 @@ set of keys over given range of frames. import click import collections +import json import matplotlib.pyplot import numpy import operator @@ -43,6 +44,12 @@ import termtables 'between Physics Actors and physics_time_taken. Format: --plot .') @click.option('--stats', type=str, multiple=True, help='Print table with stats for a given metric containing min, max, mean, median etc.') +@click.option('--stats_sum', is_flag=True, + help='Add a row to stats table for a sum per frame of all given stats metrics.') +@click.option('--stats_sort_by', type=str, default=None, multiple=True, + help='Sort stats table by given fields (source, key, sum, min, max etc).') +@click.option('--stats_table_format', type=click.Choice(['markdown', 'json']), default='markdown', + help='Print table with stats in given format.') @click.option('--precision', type=int, help='Format floating point numbers with given precision') @click.option('--timeseries_sum', is_flag=True, @@ -51,8 +58,6 @@ import termtables help='Add a graph to timeseries for a sum per frame of all given commulative timeseries.') @click.option('--timeseries_delta_sum', is_flag=True, help='Add a graph to timeseries for a sum per frame of all given timeseries delta.') -@click.option('--stats_sum', is_flag=True, - help='Add a row to stats table for a sum per frame of all given stats metrics.') @click.option('--begin_frame', type=int, default=0, help='Start processing from this frame.') @click.option('--end_frame', type=int, default=sys.maxsize, @@ -67,14 +72,12 @@ import termtables help='Threshold for hist_over.') @click.option('--show_common_path_prefix', is_flag=True, help='Show common path prefix when applied to multiple files.') -@click.option('--stats_sort_by', type=str, default=None, multiple=True, - help='Sort stats table by given fields (source, key, sum, min, max etc).') @click.argument('path', type=click.Path(), nargs=-1) def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plot, stats, precision, timeseries_sum, stats_sum, begin_frame, end_frame, path, commulative_timeseries, commulative_timeseries_sum, frame_number_name, hist_threshold, threshold_name, threshold_value, show_common_path_prefix, stats_sort_by, - timeseries_delta, timeseries_delta_sum): + timeseries_delta, timeseries_delta_sum, stats_table_format): sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))} if not show_common_path_prefix and len(sources) > 1: longest_common_prefix = os.path.commonprefix(list(sources.keys())) @@ -109,7 +112,8 @@ def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plo if plot: draw_plots(sources=frames, plots=plot) if stats: - print_stats(sources=frames, keys=matching_keys(stats), stats_sum=stats_sum, precision=precision, sort_by=stats_sort_by) + print_stats(sources=frames, keys=matching_keys(stats), stats_sum=stats_sum, precision=precision, + sort_by=stats_sort_by, table_format=stats_table_format) if hist_threshold: draw_hist_threshold(sources=frames, keys=matching_keys(hist_threshold), begin_frame=begin_frame, threshold_name=threshold_name, threshold_value=threshold_value) @@ -291,7 +295,7 @@ def draw_plots(sources, plots): fig.canvas.manager.set_window_title('plots') -def print_stats(sources, keys, stats_sum, precision, sort_by): +def print_stats(sources, keys, stats_sum, precision, sort_by, table_format): stats = list() for name, frames in sources.items(): for key in keys: @@ -301,11 +305,22 @@ def print_stats(sources, keys, stats_sum, precision, sort_by): metrics = list(stats[0].keys()) if sort_by: stats.sort(key=operator.itemgetter(*sort_by)) - termtables.print( - [list(v.values()) for v in stats], - header=metrics, - style=termtables.styles.markdown, - ) + if table_format == 'markdown': + termtables.print( + [list(v.values()) for v in stats], + header=metrics, + style=termtables.styles.markdown, + ) + elif table_format == 'json': + table = list() + for row in stats: + row_table = dict() + for key, value in zip(metrics, row.values()): + row_table[key] = value + table.append(row_table) + print(json.dumps(table)) + else: + print(f'Unsupported table format: {table_format}') def draw_hist_threshold(sources, keys, begin_frame, threshold_name, threshold_value): From 16410d09608ba3b41550b0eda2c2f6029dce068c Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 15 Mar 2024 23:52:55 +0100 Subject: [PATCH 262/451] Use std::string for ResourceManager cache key Otherwise terrain textures cache has zero hits because it stores not normalized paths. Due to implicit conversion it's possible to add entry with addEntryToObjectCache passing a string that is converted into normalized path. But then getRefFromObjectCache called with original value does not find this entry because it's not converted and overloaded operators are used instead. --- components/resource/niffilemanager.cpp | 2 +- components/resource/resourcemanager.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index 0cc48d4247..c66c7de849 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -52,7 +52,7 @@ namespace Resource Nif::Reader reader(*file, mEncoder); reader.parse(mVFS->get(name)); obj = new NifFileHolder(file); - mCache->addEntryToObjectCache(name, obj); + mCache->addEntryToObjectCache(name.value(), obj); return file; } } diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index e2626665c8..63ec95de63 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -72,7 +72,7 @@ namespace Resource double mExpiryDelay; }; - class ResourceManager : public GenericResourceManager + class ResourceManager : public GenericResourceManager { public: explicit ResourceManager(const VFS::Manager* vfs, double expiryDelay) From 9b412bc802a30a91328e2dc288782e84b6e29e08 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Mar 2024 01:55:14 +0100 Subject: [PATCH 263/451] Fix benchmark warning: -Wdeprecated-declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Example: apps/benchmarks/settings/access.cpp: In function ‘void {anonymous}::localStatic(benchmark::State&)’: apps/benchmarks/settings/access.cpp:43:37: warning: ‘typename std::enable_if<(std::is_trivially_copyable<_Tp>::value && (sizeof (Tp) <= sizeof (Tp*)))>::type benchmark::DoNotOptimize(const Tp&) [with Tp = float; typename std::enable_if<(std::is_trivially_copyable<_Tp>::value && (sizeof (Tp) <= sizeof (Tp*)))>::type = void]’ is deprecated: The const-ref version of this method can permit undesired compiler optimizations in benchmarks [-Wdeprecated-declarations] 43 | benchmark::DoNotOptimize(v); | ~~~~~~~~~~~~~~~~~~~~~~~~^~~ --- apps/benchmarks/settings/access.cpp | 30 +++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/benchmarks/settings/access.cpp b/apps/benchmarks/settings/access.cpp index aecac2dac8..7660d0d55e 100644 --- a/apps/benchmarks/settings/access.cpp +++ b/apps/benchmarks/settings/access.cpp @@ -38,7 +38,7 @@ namespace { for (auto _ : state) { - static const float v = Settings::Manager::getFloat("sky blending start", "Fog"); + static float v = Settings::Manager::getFloat("sky blending start", "Fog"); benchmark::DoNotOptimize(v); } } @@ -47,8 +47,8 @@ namespace { for (auto _ : state) { - static const float v1 = Settings::Manager::getFloat("near clip", "Camera"); - static const bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); + static float v1 = Settings::Manager::getFloat("near clip", "Camera"); + static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); benchmark::DoNotOptimize(v1); benchmark::DoNotOptimize(v2); } @@ -58,9 +58,9 @@ namespace { for (auto _ : state) { - static const float v1 = Settings::Manager::getFloat("near clip", "Camera"); - static const bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); - static const int v3 = Settings::Manager::getInt("reflection detail", "Water"); + static float v1 = Settings::Manager::getFloat("near clip", "Camera"); + static bool v2 = Settings::Manager::getBool("transparent postpass", "Post Processing"); + static int v3 = Settings::Manager::getInt("reflection detail", "Water"); benchmark::DoNotOptimize(v1); benchmark::DoNotOptimize(v2); benchmark::DoNotOptimize(v3); @@ -71,7 +71,8 @@ namespace { for (auto _ : state) { - benchmark::DoNotOptimize(Settings::fog().mSkyBlendingStart.get()); + float v = Settings::fog().mSkyBlendingStart.get(); + benchmark::DoNotOptimize(v); } } @@ -79,8 +80,10 @@ namespace { for (auto _ : state) { - benchmark::DoNotOptimize(Settings::postProcessing().mTransparentPostpass.get()); - benchmark::DoNotOptimize(Settings::camera().mNearClip.get()); + bool v1 = Settings::postProcessing().mTransparentPostpass.get(); + float v2 = Settings::camera().mNearClip.get(); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); } } @@ -88,9 +91,12 @@ namespace { for (auto _ : state) { - benchmark::DoNotOptimize(Settings::postProcessing().mTransparentPostpass.get()); - benchmark::DoNotOptimize(Settings::camera().mNearClip.get()); - benchmark::DoNotOptimize(Settings::water().mReflectionDetail.get()); + bool v1 = Settings::postProcessing().mTransparentPostpass.get(); + float v2 = Settings::camera().mNearClip.get(); + int v3 = Settings::water().mReflectionDetail.get(); + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + benchmark::DoNotOptimize(v3); } } From 9ae7b542c63aaedb29cbc0178ed903268150e4a1 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Mar 2024 02:34:46 +0100 Subject: [PATCH 264/451] Fix warning: -Wmaybe-uninitialized MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In file included from apps/opencs/model/world/pathgrid.hpp:7, from apps/opencs/model/world/idcollection.hpp:15, from apps/opencs/model/world/idcollection.cpp:1: In constructor ‘constexpr ESM::Pathgrid::Pathgrid(ESM::Pathgrid&&)’, inlined from ‘constexpr CSMWorld::Pathgrid::Pathgrid(CSMWorld::Pathgrid&&)’ at apps/opencs/model/world/pathgrid.hpp:24:12, inlined from ‘constexpr CSMWorld::Record::Record(CSMWorld::Record&&)’ at apps/opencs/model/world/record.hpp:39:12, inlined from ‘std::__detail::__unique_ptr_t<_Tp> std::make_unique(_Args&& ...) [with _Tp = CSMWorld::Record; _Args = {CSMWorld::Record}]’ at /usr/include/c++/13.2.1/bits/unique_ptr.h:1070:30, inlined from ‘std::unique_ptr CSMWorld::Record::modifiedCopy() const [with ESXRecordT = CSMWorld::Pathgrid]’ at apps/opencs/model/world/record.hpp:92:116: components/esm3/loadpgrd.hpp:19:12: warning: ‘.CSMWorld::Record::mBase.CSMWorld::Pathgrid::.ESM::Pathgrid::mData’ may be used uninitialized [-Wmaybe-uninitialized] 19 | struct Pathgrid | ^~~~~~~~ In file included from apps/opencs/model/world/idcollection.hpp:8: apps/opencs/model/world/record.hpp: In member function ‘std::unique_ptr CSMWorld::Record::modifiedCopy() const [with ESXRecordT = CSMWorld::Pathgrid]’: apps/opencs/model/world/record.hpp:92:53: note: ‘’ declared here 92 | return std::make_unique>(Record(State_ModifiedOnly, nullptr, &(this->get()))); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --- apps/opencs/model/world/record.hpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/record.hpp b/apps/opencs/model/world/record.hpp index d1f64fbfef..35e4c82a35 100644 --- a/apps/opencs/model/world/record.hpp +++ b/apps/opencs/model/world/record.hpp @@ -19,6 +19,11 @@ namespace CSMWorld State mState; + explicit RecordBase(State state) + : mState(state) + { + } + virtual ~RecordBase() = default; virtual std::unique_ptr clone() const = 0; @@ -69,21 +74,18 @@ namespace CSMWorld template Record::Record() - : mBase() + : RecordBase(State_BaseOnly) + , mBase() , mModified() { } template Record::Record(State state, const ESXRecordT* base, const ESXRecordT* modified) + : RecordBase(state) + , mBase(base == nullptr ? ESXRecordT{} : *base) + , mModified(modified == nullptr ? ESXRecordT{} : *modified) { - if (base) - mBase = *base; - - if (modified) - mModified = *modified; - - this->mState = state; } template From aa0c9fb4cbeaba895479dc8c9bb2f188f6e5856e Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 16 Mar 2024 07:45:52 +0000 Subject: [PATCH 265/451] Fix: cannot drag region into map, map columns are rectangular --- apps/opencs/view/world/regionmap.cpp | 18 +++++++++++++++++- apps/opencs/view/world/regionmap.hpp | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index a2847848d0..5c3aa11c8e 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -224,6 +224,10 @@ CSVWorld::RegionMap::RegionMap(const CSMWorld::UniversalId& universalId, CSMDoc: addAction(mViewInTableAction); setAcceptDrops(true); + + // Make columns square incase QSizeHint doesnt apply + for (int column = 0; column < this->model()->columnCount(); ++column) + this->setColumnWidth(column, this->rowHeight(0)); } void CSVWorld::RegionMap::selectAll() @@ -358,12 +362,24 @@ std::vector CSVWorld::RegionMap::getDraggedRecords() cons return ids; } +void CSVWorld::RegionMap::dragMoveEvent(QDragMoveEvent* event) +{ + QModelIndex index = indexAt(event->pos()); + const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData()); + if (mime != nullptr && (mime->holdsType(CSMWorld::UniversalId::Type_Region))) + { + event->accept(); + return; + } + + event->ignore(); +} + void CSVWorld::RegionMap::dropEvent(QDropEvent* event) { QModelIndex index = indexAt(event->pos()); bool exists = QTableView::model()->data(index, Qt::BackgroundRole) != QBrush(Qt::DiagCrossPattern); - if (!index.isValid() || !exists) { return; diff --git a/apps/opencs/view/world/regionmap.hpp b/apps/opencs/view/world/regionmap.hpp index b6c5078ea3..137b47ed83 100644 --- a/apps/opencs/view/world/regionmap.hpp +++ b/apps/opencs/view/world/regionmap.hpp @@ -59,6 +59,8 @@ namespace CSVWorld void mouseMoveEvent(QMouseEvent* event) override; + void dragMoveEvent(QDragMoveEvent* event) override; + void dropEvent(QDropEvent* event) override; public: From 5fca45565c2822a109240fa1fb3bae0f90ec6741 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 16 Mar 2024 07:46:21 +0000 Subject: [PATCH 266/451] Feature: display different brush for land vs water --- apps/opencs/model/world/regionmap.cpp | 33 +++++++++++++++++++++------ apps/opencs/model/world/regionmap.hpp | 3 ++- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index 5c22aedf4d..c27846a890 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -1,7 +1,9 @@ #include "regionmap.hpp" +#include #include #include +#include #include #include @@ -21,20 +23,36 @@ #include "data.hpp" #include "universalid.hpp" +namespace CSMWorld +{ + float getLandHeight(const CSMWorld::Cell& cell, CSMWorld::Data& data) + { + const IdCollection& lands = data.getLand(); + int landIndex = lands.searchId(cell.mId); + const Land& land = lands.getRecord(landIndex).get(); + + // If any part of land is above water, returns > 0 - otherwise returns < 0 + if (land.getLandData()) + return land.getLandData()->mMaxHeight - cell.mWater; + + return 0.0f; + } +} + CSMWorld::RegionMap::CellDescription::CellDescription() : mDeleted(false) { } -CSMWorld::RegionMap::CellDescription::CellDescription(const Record& cell) +CSMWorld::RegionMap::CellDescription::CellDescription(const Record& cell, float landHeight) { const Cell& cell2 = cell.get(); if (!cell2.isExterior()) throw std::logic_error("Interior cell in region map"); + mMaxLandHeight = landHeight; mDeleted = cell.isDeleted(); - mRegion = cell2.mRegion; mName = cell2.mName; } @@ -92,7 +110,7 @@ void CSMWorld::RegionMap::buildMap() if (cell2.isExterior()) { - CellDescription description(cell); + CellDescription description(cell, getLandHeight(cell2, mData)); CellCoordinates index = getIndex(cell2); @@ -140,7 +158,7 @@ void CSMWorld::RegionMap::addCells(int start, int end) { CellCoordinates index = getIndex(cell2); - CellDescription description(cell); + CellDescription description(cell, getLandHeight(cell.get(), mData)); addCell(index, description); } @@ -335,10 +353,11 @@ QVariant CSMWorld::RegionMap::data(const QModelIndex& index, int role) const auto iter = mColours.find(cell->second.mRegion); if (iter != mColours.end()) - return QBrush(QColor(iter->second & 0xff, (iter->second >> 8) & 0xff, (iter->second >> 16) & 0xff)); + return QBrush(QColor(iter->second & 0xff, (iter->second >> 8) & 0xff, (iter->second >> 16) & 0xff), + cell->second.mMaxLandHeight > 0 ? Qt::SolidPattern : Qt::CrossPattern); - if (cell->second.mRegion.empty()) - return QBrush(Qt::Dense6Pattern); // no region + if (cell->second.mRegion.empty()) // no region + return QBrush(cell->second.mMaxLandHeight > 0 ? Qt::Dense3Pattern : Qt::Dense6Pattern); return QBrush(Qt::red, Qt::Dense6Pattern); // invalid region } diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp index 3f62c7b61d..e5a4d61337 100644 --- a/apps/opencs/model/world/regionmap.hpp +++ b/apps/opencs/model/world/regionmap.hpp @@ -40,13 +40,14 @@ namespace CSMWorld private: struct CellDescription { + float mMaxLandHeight; bool mDeleted; ESM::RefId mRegion; std::string mName; CellDescription(); - CellDescription(const Record& cell); + CellDescription(const Record& cell, float landHeight); }; Data& mData; From a62da201e5516ba677ade73214bb3b161ddd4a05 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 16 Mar 2024 09:02:47 +0000 Subject: [PATCH 267/451] check for land index not -1, fix warning Signed-off-by: Sam Hellawell --- apps/opencs/model/world/regionmap.cpp | 4 +++- apps/opencs/view/world/regionmap.cpp | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index c27846a890..f555f0ea32 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -29,9 +29,11 @@ namespace CSMWorld { const IdCollection& lands = data.getLand(); int landIndex = lands.searchId(cell.mId); - const Land& land = lands.getRecord(landIndex).get(); + if (landIndex == -1) + return 0.0f; // If any part of land is above water, returns > 0 - otherwise returns < 0 + const Land& land = lands.getRecord(landIndex).get(); if (land.getLandData()) return land.getLandData()->mMaxHeight - cell.mWater; diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 5c3aa11c8e..17d0016afc 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -364,7 +364,6 @@ std::vector CSVWorld::RegionMap::getDraggedRecords() cons void CSVWorld::RegionMap::dragMoveEvent(QDragMoveEvent* event) { - QModelIndex index = indexAt(event->pos()); const CSMWorld::TableMimeData* mime = dynamic_cast(event->mimeData()); if (mime != nullptr && (mime->holdsType(CSMWorld::UniversalId::Type_Region))) { From ee2cc8aeb7b377127ff5b80acc2aa62b226f1b86 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Mar 2024 13:07:56 +0100 Subject: [PATCH 268/451] Fix build with MSVC 19.38 components\detournavigator\navigator.hpp(44): error C3861: 'assert': identifier not found --- components/detournavigator/navigator.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/detournavigator/navigator.hpp b/components/detournavigator/navigator.hpp index f2acc8c9d6..378af081d0 100644 --- a/components/detournavigator/navigator.hpp +++ b/components/detournavigator/navigator.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H #define OPENMW_COMPONENTS_DETOURNAVIGATOR_NAVIGATOR_H +#include #include #include "heightfieldshape.hpp" From 4520ee465d3d9cf9c5d2da58d79340f58abd0555 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 16 Mar 2024 16:26:26 +0400 Subject: [PATCH 269/451] Do not copy vector --- components/lua/yamlloader.cpp | 2 +- components/lua/yamlloader.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/lua/yamlloader.cpp b/components/lua/yamlloader.cpp index 14553cfac4..41b8fb346f 100644 --- a/components/lua/yamlloader.cpp +++ b/components/lua/yamlloader.cpp @@ -25,7 +25,7 @@ namespace LuaUtil return load(rootNodes, lua); } - sol::object YamlLoader::load(const std::vector rootNodes, const sol::state_view& lua) + sol::object YamlLoader::load(const std::vector& rootNodes, const sol::state_view& lua) { if (rootNodes.empty()) return sol::nil; diff --git a/components/lua/yamlloader.hpp b/components/lua/yamlloader.hpp index 1ca95223cd..2ad9315149 100644 --- a/components/lua/yamlloader.hpp +++ b/components/lua/yamlloader.hpp @@ -30,7 +30,7 @@ namespace LuaUtil String }; - static sol::object load(const std::vector rootNodes, const sol::state_view& lua); + static sol::object load(const std::vector& rootNodes, const sol::state_view& lua); static sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); From 8037ad7f001582655cc5136ce3c1148e6bfffc55 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 16 Mar 2024 16:59:48 +0400 Subject: [PATCH 270/451] Remove unused includes --- apps/openmw_test_suite/lua/test_yaml.cpp | 1 - components/lua/yamlloader.hpp | 2 -- 2 files changed, 3 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_yaml.cpp b/apps/openmw_test_suite/lua/test_yaml.cpp index c7d484cf51..198cf1d0b5 100644 --- a/apps/openmw_test_suite/lua/test_yaml.cpp +++ b/apps/openmw_test_suite/lua/test_yaml.cpp @@ -1,4 +1,3 @@ -#include "gmock/gmock.h" #include #include diff --git a/components/lua/yamlloader.hpp b/components/lua/yamlloader.hpp index 2ad9315149..0ba2e5a1f1 100644 --- a/components/lua/yamlloader.hpp +++ b/components/lua/yamlloader.hpp @@ -1,9 +1,7 @@ #ifndef COMPONENTS_LUA_YAMLLOADER_H #define COMPONENTS_LUA_YAMLLOADER_H -#include #include -#include #include namespace LuaUtil From b657cb2e4ca138848657408e743f0a7e42b1784e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 16 Mar 2024 17:22:07 +0400 Subject: [PATCH 271/451] Simplify code --- apps/openmw/mwlua/markupbindings.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/markupbindings.cpp b/apps/openmw/mwlua/markupbindings.cpp index 997674b45d..dacdb7ee2c 100644 --- a/apps/openmw/mwlua/markupbindings.cpp +++ b/apps/openmw/mwlua/markupbindings.cpp @@ -19,8 +19,7 @@ namespace MWLua auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); api["loadYaml"] = [lua = context.mLua, vfs](std::string_view fileName) { - auto normalizedName = VFS::Path::normalizeFilename(fileName); - auto file = vfs->getNormalized(normalizedName); + Files::IStreamPtr file = vfs->get(VFS::Path::Normalized(fileName)); return LuaUtil::YamlLoader::load(*file, lua->sol()); }; api["decodeYaml"] = [lua = context.mLua](std::string_view inputData) { From 2523afe9c2d5adf066dafba9395322986002217e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 16 Mar 2024 17:22:25 +0400 Subject: [PATCH 272/451] Use namespace instead of static class --- apps/openmw_test_suite/lua/test_yaml.cpp | 2 + components/lua/yamlloader.cpp | 414 ++++++++++++----------- components/lua/yamlloader.hpp | 38 +-- 3 files changed, 227 insertions(+), 227 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_yaml.cpp b/apps/openmw_test_suite/lua/test_yaml.cpp index 198cf1d0b5..759834c5e1 100644 --- a/apps/openmw_test_suite/lua/test_yaml.cpp +++ b/apps/openmw_test_suite/lua/test_yaml.cpp @@ -1,5 +1,7 @@ #include +#include + #include #include "../testing_util.hpp" diff --git a/components/lua/yamlloader.cpp b/components/lua/yamlloader.cpp index 41b8fb346f..7e2736aa8e 100644 --- a/components/lua/yamlloader.cpp +++ b/components/lua/yamlloader.cpp @@ -3,239 +3,267 @@ #include #include +#include + #include #include namespace LuaUtil { - namespace + namespace YamlLoader { constexpr uint64_t maxDepth = 250; - } - - sol::object YamlLoader::load(const std::string& input, const sol::state_view& lua) - { - std::vector rootNodes = YAML::LoadAll(input); - return LuaUtil::YamlLoader::load(rootNodes, lua); - } - - sol::object YamlLoader::load(std::istream& input, const sol::state_view& lua) - { - std::vector rootNodes = YAML::LoadAll(input); - return load(rootNodes, lua); - } - sol::object YamlLoader::load(const std::vector& rootNodes, const sol::state_view& lua) - { - if (rootNodes.empty()) - return sol::nil; - - if (rootNodes.size() == 1) - return getNode(rootNodes[0], lua, 0); - - sol::table documentsTable(lua, sol::create); - for (const auto& root : rootNodes) + enum class ScalarType { - documentsTable.add(getNode(root, lua, 1)); - } + Boolean, + Decimal, + Float, + Hexadecimal, + Infinity, + NotNumber, + Null, + Octal, + String + }; - return documentsTable; - } + sol::object load(const std::vector& rootNodes, const sol::state_view& lua); - sol::object YamlLoader::getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) - { - if (depth >= maxDepth) - throw std::runtime_error("Maximum layers depth exceeded, probably caused by a circular reference"); + sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); - ++depth; + sol::table getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); - if (node.IsMap()) - return getMap(node, lua, depth); - else if (node.IsSequence()) - return getArray(node, lua, depth); - else if (node.IsScalar()) - return getScalar(node, lua); - else if (node.IsNull()) - return sol::nil; + sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); - nodeError(node, "An unknown YAML node encountered"); - } + ScalarType getScalarType(const YAML::Node& node); - sol::table YamlLoader::getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) - { - sol::table childTable(lua, sol::create); + sol::object getScalar(const YAML::Node& node, const sol::state_view& lua); - for (const auto& pair : node) + [[noreturn]] void nodeError(const YAML::Node& node, const std::string& message); + + sol::object load(const std::string& input, const sol::state_view& lua) { - if (pair.first.IsMap()) - nodeError(pair.first, "Only scalar nodes can be used as keys, encountered map instead"); - if (pair.first.IsSequence()) - nodeError(pair.first, "Only scalar nodes can be used as keys, encountered array instead"); - if (pair.first.IsNull()) - nodeError(pair.first, "Only scalar nodes can be used as keys, encountered null instead"); - - auto key = getNode(pair.first, lua, depth); - if (key.get_type() == sol::type::number && std::isnan(key.as())) - nodeError(pair.first, "Only scalar nodes can be used as keys, encountered nan instead"); - - childTable[key] = getNode(pair.second, lua, depth); + std::vector rootNodes = YAML::LoadAll(input); + return load(rootNodes, lua); } - return childTable; - } - - sol::table YamlLoader::getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) - { - sol::table childTable(lua, sol::create); - - for (const auto& child : node) + sol::object load(const std::vector& rootNodes, const sol::state_view& lua) { - childTable.add(getNode(child, lua, depth)); - } + if (rootNodes.empty()) + return sol::nil; - return childTable; - } + if (rootNodes.size() == 1) + return getNode(rootNodes[0], lua, 0); - YamlLoader::ScalarType YamlLoader::getScalarType(const YAML::Node& node) - { - const auto& tag = node.Tag(); - const auto& value = node.Scalar(); - if (tag == "!") - return ScalarType::String; + sol::table documentsTable(lua, sol::create); + for (const auto& root : rootNodes) + { + documentsTable.add(getNode(root, lua, 1)); + } - // Note that YAML allows to explicitely specify a scalar type via tag (e.g. "!!bool"), but it makes no - // sense in Lua: - // 1. Both integers and floats use the "number" type prior to Lua 5.3 - // 2. Strings can be quoted, which is more readable than "!!str" - // 3. Most of possible conversions are invalid or their result is unclear - // So ignore this feature for now. - if (tag != "?") - nodeError(node, "An invalid tag'" + tag + "' encountered"); - - if (value.empty()) - return ScalarType::Null; - - // Resolve type according to YAML 1.2 Core Schema (see https://yaml.org/spec/1.2.2/#103-core-schema) - static const std::regex boolRegex("true|True|TRUE|false|False|FALSE", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), boolRegex)) - return ScalarType::Boolean; - - static const std::regex decimalRegex("[-+]?[0-9]+", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), decimalRegex)) - return ScalarType::Decimal; - - static const std::regex floatRegex( - "[-+]?([.][0-9]+|[0-9]+([.][0-9]*)?)([eE][-+]?[0-9]+)?", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), floatRegex)) - return ScalarType::Float; - - static const std::regex octalRegex("0o[0-7]+", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), octalRegex)) - return ScalarType::Octal; - - static const std::regex hexdecimalRegex("0x[0-9a-fA-F]+", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), hexdecimalRegex)) - return ScalarType::Hexadecimal; - - static const std::regex infinityRegex("[-+]?([.]inf|[.]Inf|[.]INF)", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), infinityRegex)) - return ScalarType::Infinity; - - static const std::regex nanRegex("[.]nan|[.]NaN|[.]NAN", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), nanRegex)) - return ScalarType::NotNumber; - - static const std::regex nullRegex("null|Null|NULL|~", std::regex_constants::extended); - if (std::regex_match(node.Scalar(), nullRegex)) - return ScalarType::Null; - - return ScalarType::String; - } + return documentsTable; + } - sol::object YamlLoader::getScalar(const YAML::Node& node, const sol::state_view& lua) - { - auto type = getScalarType(node); - const auto& value = node.Scalar(); + sol::object load(std::istream& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return load(rootNodes, lua); + } - switch (type) + sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) { - case ScalarType::Null: + if (depth >= maxDepth) + throw std::runtime_error("Maximum layers depth exceeded, probably caused by a circular reference"); + + ++depth; + + if (node.IsMap()) + return getMap(node, lua, depth); + else if (node.IsSequence()) + return getArray(node, lua, depth); + else if (node.IsScalar()) + return getScalar(node, lua); + else if (node.IsNull()) return sol::nil; - case ScalarType::String: - return sol::make_object(lua, value); - case ScalarType::NotNumber: - return sol::make_object(lua, std::nan("")); - case ScalarType::Infinity: - { - if (!value.empty() && value[0] == '-') - return sol::make_object(lua, -std::numeric_limits::infinity()); - return sol::make_object(lua, std::numeric_limits::infinity()); - } - case ScalarType::Boolean: - { - if (Misc::StringUtils::lowerCase(value) == "true") - return sol::make_object(lua, true); + nodeError(node, "An unknown YAML node encountered"); + } - if (Misc::StringUtils::lowerCase(value) == "false") - return sol::make_object(lua, false); + sol::table getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + sol::table childTable(lua, sol::create); - nodeError(node, "Can not read a boolean value '" + value + "'"); - } - case ScalarType::Decimal: + for (const auto& pair : node) { - int offset = 0; + if (pair.first.IsMap()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered map instead"); + if (pair.first.IsSequence()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered array instead"); + if (pair.first.IsNull()) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered null instead"); + + auto key = getNode(pair.first, lua, depth); + if (key.get_type() == sol::type::number && std::isnan(key.as())) + nodeError(pair.first, "Only scalar nodes can be used as keys, encountered nan instead"); + + childTable[key] = getNode(pair.second, lua, depth); + } - // std::from_chars does not support "+" sign - if (!value.empty() && value[0] == '+') - ++offset; + return childTable; + } - int result = 0; - const auto status = std::from_chars(value.data() + offset, value.data() + value.size(), result); - if (status.ec == std::errc()) - return sol::make_object(lua, result); + sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + sol::table childTable(lua, sol::create); - nodeError(node, "Can not read a decimal value '" + value + "'"); - } - case ScalarType::Float: + for (const auto& child : node) { - // Not all compilers support std::from_chars for floats - double result = 0.0; - bool success = YAML::convert::decode(node, result); - if (success) - return sol::make_object(lua, result); - - nodeError(node, "Can not read a float value '" + value + "'"); + childTable.add(getNode(child, lua, depth)); } - case ScalarType::Hexadecimal: - { - int result = 0; - const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 16); - if (status.ec == std::errc()) - return sol::make_object(lua, result); - nodeError(node, "Can not read a hexadecimal value '" + value + "'"); - } - case ScalarType::Octal: - { - int result = 0; - const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 8); - if (status.ec == std::errc()) - return sol::make_object(lua, result); + return childTable; + } + + ScalarType getScalarType(const YAML::Node& node) + { + const auto& tag = node.Tag(); + const auto& value = node.Scalar(); + if (tag == "!") + return ScalarType::String; + + // Note that YAML allows to explicitely specify a scalar type via tag (e.g. "!!bool"), but it makes no + // sense in Lua: + // 1. Both integers and floats use the "number" type prior to Lua 5.3 + // 2. Strings can be quoted, which is more readable than "!!str" + // 3. Most of possible conversions are invalid or their result is unclear + // So ignore this feature for now. + if (tag != "?") + nodeError(node, "An invalid tag'" + tag + "' encountered"); + + if (value.empty()) + return ScalarType::Null; + + // Resolve type according to YAML 1.2 Core Schema (see https://yaml.org/spec/1.2.2/#103-core-schema) + static const std::regex boolRegex("true|True|TRUE|false|False|FALSE", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), boolRegex)) + return ScalarType::Boolean; + + static const std::regex decimalRegex("[-+]?[0-9]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), decimalRegex)) + return ScalarType::Decimal; + + static const std::regex floatRegex( + "[-+]?([.][0-9]+|[0-9]+([.][0-9]*)?)([eE][-+]?[0-9]+)?", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), floatRegex)) + return ScalarType::Float; + + static const std::regex octalRegex("0o[0-7]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), octalRegex)) + return ScalarType::Octal; + + static const std::regex hexdecimalRegex("0x[0-9a-fA-F]+", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), hexdecimalRegex)) + return ScalarType::Hexadecimal; + + static const std::regex infinityRegex("[-+]?([.]inf|[.]Inf|[.]INF)", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), infinityRegex)) + return ScalarType::Infinity; + + static const std::regex nanRegex("[.]nan|[.]NaN|[.]NAN", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), nanRegex)) + return ScalarType::NotNumber; + + static const std::regex nullRegex("null|Null|NULL|~", std::regex_constants::extended); + if (std::regex_match(node.Scalar(), nullRegex)) + return ScalarType::Null; - nodeError(node, "Can not read an octal value '" + value + "'"); + return ScalarType::String; + } + + sol::object getScalar(const YAML::Node& node, const sol::state_view& lua) + { + auto type = getScalarType(node); + const auto& value = node.Scalar(); + + switch (type) + { + case ScalarType::Null: + return sol::nil; + case ScalarType::String: + return sol::make_object(lua, value); + case ScalarType::NotNumber: + return sol::make_object(lua, std::nan("")); + case ScalarType::Infinity: + { + if (!value.empty() && value[0] == '-') + return sol::make_object(lua, -std::numeric_limits::infinity()); + + return sol::make_object(lua, std::numeric_limits::infinity()); + } + case ScalarType::Boolean: + { + if (Misc::StringUtils::lowerCase(value) == "true") + return sol::make_object(lua, true); + + if (Misc::StringUtils::lowerCase(value) == "false") + return sol::make_object(lua, false); + + nodeError(node, "Can not read a boolean value '" + value + "'"); + } + case ScalarType::Decimal: + { + int offset = 0; + + // std::from_chars does not support "+" sign + if (!value.empty() && value[0] == '+') + ++offset; + + int result = 0; + const auto status = std::from_chars(value.data() + offset, value.data() + value.size(), result); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a decimal value '" + value + "'"); + } + case ScalarType::Float: + { + // Not all compilers support std::from_chars for floats + double result = 0.0; + bool success = YAML::convert::decode(node, result); + if (success) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a float value '" + value + "'"); + } + case ScalarType::Hexadecimal: + { + int result = 0; + const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 16); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read a hexadecimal value '" + value + "'"); + } + case ScalarType::Octal: + { + int result = 0; + const auto status = std::from_chars(value.data() + 2, value.data() + value.size(), result, 8); + if (status.ec == std::errc()) + return sol::make_object(lua, result); + + nodeError(node, "Can not read an octal value '" + value + "'"); + } + default: + nodeError(node, "An unknown scalar '" + value + "' encountered"); } - default: - nodeError(node, "An unknown scalar '" + value + "' encountered"); } - } - [[noreturn]] void YamlLoader::nodeError(const YAML::Node& node, const std::string& message) - { - const auto& mark = node.Mark(); - std::string error = Misc::StringUtils::format( - " at line=%d column=%d position=%d", mark.line + 1, mark.column + 1, mark.pos + 1); - throw std::runtime_error(message + error); + [[noreturn]] void nodeError(const YAML::Node& node, const std::string& message) + { + const auto& mark = node.Mark(); + std::string error = Misc::StringUtils::format( + " at line=%d column=%d position=%d", mark.line + 1, mark.column + 1, mark.pos + 1); + throw std::runtime_error(message + error); + } } - } diff --git a/components/lua/yamlloader.hpp b/components/lua/yamlloader.hpp index 0ba2e5a1f1..c3339d7eb3 100644 --- a/components/lua/yamlloader.hpp +++ b/components/lua/yamlloader.hpp @@ -2,46 +2,16 @@ #define COMPONENTS_LUA_YAMLLOADER_H #include -#include namespace LuaUtil { - class YamlLoader + namespace YamlLoader { - public: - static sol::object load(const std::string& input, const sol::state_view& lua); + sol::object load(const std::string& input, const sol::state_view& lua); - static sol::object load(std::istream& input, const sol::state_view& lua); - - private: - enum class ScalarType - { - Boolean, - Decimal, - Float, - Hexadecimal, - Infinity, - NotNumber, - Null, - Octal, - String - }; - - static sol::object load(const std::vector& rootNodes, const sol::state_view& lua); - - static sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); - - static sol::table getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); - - static sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); - - static ScalarType getScalarType(const YAML::Node& node); - - static sol::object getScalar(const YAML::Node& node, const sol::state_view& lua); - - [[noreturn]] static void nodeError(const YAML::Node& node, const std::string& message); - }; + sol::object load(std::istream& input, const sol::state_view& lua); + } } From cb831a59178a065c3f1bc76cea186ca55fd6e252 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 17 Mar 2024 17:22:10 +0400 Subject: [PATCH 273/451] Add more includes just for sure --- components/lua/yamlloader.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/lua/yamlloader.cpp b/components/lua/yamlloader.cpp index 7e2736aa8e..30323fe116 100644 --- a/components/lua/yamlloader.cpp +++ b/components/lua/yamlloader.cpp @@ -1,7 +1,12 @@ #include "yamlloader.hpp" #include +#include +#include #include +#include +#include +#include #include From 2d3a8ca0fc77f6d1532e9e6086b292b4a1275cf5 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 17 Mar 2024 18:15:23 +0400 Subject: [PATCH 274/451] Do not use an inner namespace --- apps/openmw/mwlua/markupbindings.cpp | 4 +- apps/openmw_test_suite/lua/test_yaml.cpp | 54 ++++++++++++------------ components/lua/yamlloader.cpp | 36 +++++++++------- components/lua/yamlloader.hpp | 14 +++--- 4 files changed, 57 insertions(+), 51 deletions(-) diff --git a/apps/openmw/mwlua/markupbindings.cpp b/apps/openmw/mwlua/markupbindings.cpp index dacdb7ee2c..f0b9d67a51 100644 --- a/apps/openmw/mwlua/markupbindings.cpp +++ b/apps/openmw/mwlua/markupbindings.cpp @@ -20,10 +20,10 @@ namespace MWLua api["loadYaml"] = [lua = context.mLua, vfs](std::string_view fileName) { Files::IStreamPtr file = vfs->get(VFS::Path::Normalized(fileName)); - return LuaUtil::YamlLoader::load(*file, lua->sol()); + return LuaUtil::loadYaml(*file, lua->sol()); }; api["decodeYaml"] = [lua = context.mLua](std::string_view inputData) { - return LuaUtil::YamlLoader::load(std::string(inputData), lua->sol()); + return LuaUtil::loadYaml(std::string(inputData), lua->sol()); }; return LuaUtil::makeReadOnly(api); diff --git a/apps/openmw_test_suite/lua/test_yaml.cpp b/apps/openmw_test_suite/lua/test_yaml.cpp index 759834c5e1..fa28889440 100644 --- a/apps/openmw_test_suite/lua/test_yaml.cpp +++ b/apps/openmw_test_suite/lua/test_yaml.cpp @@ -1,17 +1,19 @@ #include +#include +#include +#include + #include #include -#include "../testing_util.hpp" - namespace { template bool checkNumber(sol::state_view& lua, const std::string& inputData, T requiredValue) { - sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + sol::object result = LuaUtil::loadYaml(inputData, lua); if (result.get_type() != sol::type::number) return false; @@ -20,7 +22,7 @@ namespace bool checkBool(sol::state_view& lua, const std::string& inputData, bool requiredValue) { - sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + sol::object result = LuaUtil::loadYaml(inputData, lua); if (result.get_type() != sol::type::boolean) return false; @@ -29,13 +31,13 @@ namespace bool checkNil(sol::state_view& lua, const std::string& inputData) { - sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + sol::object result = LuaUtil::loadYaml(inputData, lua); return result == sol::nil; } bool checkNan(sol::state_view& lua, const std::string& inputData) { - sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + sol::object result = LuaUtil::loadYaml(inputData, lua); if (result.get_type() != sol::type::number) return false; @@ -44,7 +46,7 @@ namespace bool checkString(sol::state_view& lua, const std::string& inputData, const std::string& requiredValue) { - sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + sol::object result = LuaUtil::loadYaml(inputData, lua); if (result.get_type() != sol::type::string) return false; @@ -53,7 +55,7 @@ namespace bool checkString(sol::state_view& lua, const std::string& inputData) { - sol::object result = LuaUtil::YamlLoader::load(inputData, lua); + sol::object result = LuaUtil::loadYaml(inputData, lua); if (result.get_type() != sol::type::string) return false; @@ -165,7 +167,7 @@ namespace try { YAML::Node root = YAML::Load(input); - sol::object result = LuaUtil::YamlLoader::load(input, lua); + sol::object result = LuaUtil::loadYaml(input, lua); } catch (const std::runtime_error& e) { @@ -180,29 +182,29 @@ namespace { sol::state lua; - sol::object map = LuaUtil::YamlLoader::load("{ x: , y: 2, 4: 5 }", lua); + sol::object map = LuaUtil::loadYaml("{ x: , y: 2, 4: 5 }", lua); ASSERT_EQ(map.as()["x"], sol::nil); ASSERT_EQ(map.as()["y"], 2); ASSERT_EQ(map.as()[4], 5); - sol::object array = LuaUtil::YamlLoader::load("[ 3, 4 ]", lua); + sol::object array = LuaUtil::loadYaml("[ 3, 4 ]", lua); ASSERT_EQ(array.as()[1], 3); - sol::object emptyTable = LuaUtil::YamlLoader::load("{}", lua); + sol::object emptyTable = LuaUtil::loadYaml("{}", lua); ASSERT_TRUE(emptyTable.as().empty()); - sol::object emptyArray = LuaUtil::YamlLoader::load("[]", lua); + sol::object emptyArray = LuaUtil::loadYaml("[]", lua); ASSERT_TRUE(emptyArray.as().empty()); - ASSERT_THROW(LuaUtil::YamlLoader::load("{ null: 1 }", lua), std::runtime_error); - ASSERT_THROW(LuaUtil::YamlLoader::load("{ .nan: 1 }", lua), std::runtime_error); + ASSERT_THROW(LuaUtil::loadYaml("{ null: 1 }", lua), std::runtime_error); + ASSERT_THROW(LuaUtil::loadYaml("{ .nan: 1 }", lua), std::runtime_error); const std::string scalarArrayInput = R"( - First Scalar - 1 - true)"; - sol::object scalarArray = LuaUtil::YamlLoader::load(scalarArrayInput, lua); + sol::object scalarArray = LuaUtil::loadYaml(scalarArrayInput, lua); ASSERT_EQ(scalarArray.as()[1], std::string("First Scalar")); ASSERT_EQ(scalarArray.as()[2], 1); ASSERT_EQ(scalarArray.as()[3], true); @@ -213,7 +215,7 @@ namespace float: 0.278 # Float value bool: false # Boolean value)"; - sol::object scalarMapWithComments = LuaUtil::YamlLoader::load(scalarMapWithCommentsInput, lua); + sol::object scalarMapWithComments = LuaUtil::loadYaml(scalarMapWithCommentsInput, lua); ASSERT_EQ(scalarMapWithComments.as()["string"], std::string("str")); ASSERT_EQ(scalarMapWithComments.as()["integer"], 65); ASSERT_EQ(scalarMapWithComments.as()["float"], 0.278); @@ -229,7 +231,7 @@ namespace - false - 1)"; - sol::object mapOfArrays = LuaUtil::YamlLoader::load(mapOfArraysInput, lua); + sol::object mapOfArrays = LuaUtil::loadYaml(mapOfArraysInput, lua); ASSERT_EQ(mapOfArrays.as()["x"][3], true); ASSERT_EQ(mapOfArrays.as()["y"][1], std::string("aaa")); @@ -243,7 +245,7 @@ namespace hr: 63 avg: 0.288)"; - sol::object arrayOfMaps = LuaUtil::YamlLoader::load(arrayOfMapsInput, lua); + sol::object arrayOfMaps = LuaUtil::loadYaml(arrayOfMapsInput, lua); ASSERT_EQ(arrayOfMaps.as()[1]["avg"], 0.278); ASSERT_EQ(arrayOfMaps.as()[2]["name"], std::string("Name2")); @@ -251,7 +253,7 @@ namespace - [Name1, 65, 0.278] - [Name2 , 63, 0.288])"; - sol::object arrayOfArrays = LuaUtil::YamlLoader::load(arrayOfArraysInput, lua); + sol::object arrayOfArrays = LuaUtil::loadYaml(arrayOfArraysInput, lua); ASSERT_EQ(arrayOfArrays.as()[1][2], 65); ASSERT_EQ(arrayOfArrays.as()[2][1], std::string("Name2")); @@ -262,7 +264,7 @@ namespace avg: 0.288, })"; - sol::object mapOfMaps = LuaUtil::YamlLoader::load(mapOfMapsInput, lua); + sol::object mapOfMaps = LuaUtil::loadYaml(mapOfMapsInput, lua); ASSERT_EQ(mapOfMaps.as()["Name1"]["hr"], 65); ASSERT_EQ(mapOfMaps.as()["Name2"]["avg"], 0.288); } @@ -282,7 +284,7 @@ namespace " - 3\n" " - false"; - sol::object twoDocuments = LuaUtil::YamlLoader::load(twoDocumentsInput, lua); + sol::object twoDocuments = LuaUtil::loadYaml(twoDocumentsInput, lua); ASSERT_EQ(twoDocuments.as()[1][1], std::string("First Scalar")); ASSERT_EQ(twoDocuments.as()[2][3], false); @@ -295,7 +297,7 @@ namespace - *a # Subsequent occurrence - Name2)"; - sol::object anchor = LuaUtil::YamlLoader::load(anchorInput, lua); + sol::object anchor = LuaUtil::loadYaml(anchorInput, lua); ASSERT_EQ(anchor.as()["y"][1], std::string("Value1")); const std::string compoundKeyInput = R"( @@ -307,7 +309,7 @@ namespace String4 ] : [ 2, 3, 4 ])"; - ASSERT_THROW(LuaUtil::YamlLoader::load(compoundKeyInput, lua), std::runtime_error); + ASSERT_THROW(LuaUtil::loadYaml(compoundKeyInput, lua), std::runtime_error); const std::string compactNestedMappingInput = R"( - item : Item1 @@ -317,7 +319,7 @@ namespace - item : Item3 quantity: 11)"; - sol::object compactNestedMapping = LuaUtil::YamlLoader::load(compactNestedMappingInput, lua); + sol::object compactNestedMapping = LuaUtil::loadYaml(compactNestedMappingInput, lua); ASSERT_EQ(compactNestedMapping.as()[2]["quantity"], 4); } @@ -347,7 +349,7 @@ namespace quoted: "So does this quoted scalar.\n")"; - sol::object multiLinePlanarScalars = LuaUtil::YamlLoader::load(multiLinePlanarScalarsInput, lua); + sol::object multiLinePlanarScalars = LuaUtil::loadYaml(multiLinePlanarScalarsInput, lua); ASSERT_TRUE( multiLinePlanarScalars.as()["plain"] == std::string("This unquoted scalar spans many lines.")); ASSERT_TRUE(multiLinePlanarScalars.as()["quoted"] == std::string("So does this quoted scalar.\n")); diff --git a/components/lua/yamlloader.cpp b/components/lua/yamlloader.cpp index 30323fe116..df83af6253 100644 --- a/components/lua/yamlloader.cpp +++ b/components/lua/yamlloader.cpp @@ -8,6 +8,9 @@ #include #include +#include +#include + #include #include @@ -15,7 +18,7 @@ namespace LuaUtil { - namespace YamlLoader + namespace { constexpr uint64_t maxDepth = 250; @@ -32,7 +35,7 @@ namespace LuaUtil String }; - sol::object load(const std::vector& rootNodes, const sol::state_view& lua); + sol::object loadAll(const std::vector& rootNodes, const sol::state_view& lua); sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); @@ -45,14 +48,23 @@ namespace LuaUtil sol::object getScalar(const YAML::Node& node, const sol::state_view& lua); [[noreturn]] void nodeError(const YAML::Node& node, const std::string& message); + } - sol::object load(const std::string& input, const sol::state_view& lua) - { - std::vector rootNodes = YAML::LoadAll(input); - return load(rootNodes, lua); - } + sol::object loadYaml(const std::string& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return loadAll(rootNodes, lua); + } - sol::object load(const std::vector& rootNodes, const sol::state_view& lua) + sol::object loadYaml(std::istream& input, const sol::state_view& lua) + { + std::vector rootNodes = YAML::LoadAll(input); + return loadAll(rootNodes, lua); + } + + namespace + { + sol::object loadAll(const std::vector& rootNodes, const sol::state_view& lua) { if (rootNodes.empty()) return sol::nil; @@ -69,12 +81,6 @@ namespace LuaUtil return documentsTable; } - sol::object load(std::istream& input, const sol::state_view& lua) - { - std::vector rootNodes = YAML::LoadAll(input); - return load(rootNodes, lua); - } - sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) { if (depth >= maxDepth) @@ -143,7 +149,7 @@ namespace LuaUtil // 3. Most of possible conversions are invalid or their result is unclear // So ignore this feature for now. if (tag != "?") - nodeError(node, "An invalid tag'" + tag + "' encountered"); + nodeError(node, "An invalid tag '" + tag + "' encountered"); if (value.empty()) return ScalarType::Null; diff --git a/components/lua/yamlloader.hpp b/components/lua/yamlloader.hpp index c3339d7eb3..6f28da66ce 100644 --- a/components/lua/yamlloader.hpp +++ b/components/lua/yamlloader.hpp @@ -1,18 +1,16 @@ #ifndef COMPONENTS_LUA_YAMLLOADER_H #define COMPONENTS_LUA_YAMLLOADER_H -#include +#include +#include + +#include namespace LuaUtil { + sol::object loadYaml(const std::string& input, const sol::state_view& lua); - namespace YamlLoader - { - sol::object load(const std::string& input, const sol::state_view& lua); - - sol::object load(std::istream& input, const sol::state_view& lua); - } - + sol::object loadYaml(std::istream& input, const sol::state_view& lua); } #endif // COMPONENTS_LUA_YAMLLOADER_H From fcff1a673967d6313ccba94fb9157c6dce992cb7 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 15 Mar 2024 22:23:14 -0500 Subject: [PATCH 275/451] Fix #7887, use actual instead of reported size for script data --- CHANGELOG.md | 1 + components/esm3/loadscpt.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93d9bd158c..a89eaacaaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -158,6 +158,7 @@ Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs Bug #7859: AutoCalc flag is not used to calculate potion value Bug #7872: Region sounds use wrong odds + Bug #7887: Editor: Mismatched reported script data size and actual data size causes a crash during save Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/components/esm3/loadscpt.cpp b/components/esm3/loadscpt.cpp index f79f4989ef..2eb272fe8b 100644 --- a/components/esm3/loadscpt.cpp +++ b/components/esm3/loadscpt.cpp @@ -149,6 +149,8 @@ namespace ESM if (!hasHeader) esm.fail("Missing SCHD subrecord"); + // Reported script data size is not always trustworthy, so override it with actual data size + mData.mScriptDataSize = mScriptData.size(); } void Script::save(ESMWriter& esm, bool isDeleted) const From 080245aa2643bc7112544894baa7f3ade5d6fca3 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 15 Mar 2024 21:26:51 +0100 Subject: [PATCH 276/451] Do not align arrays by duplicating last value To produce the same stats for single and multiple sources. If there are multiple sources with different number of frames, leave the number of values per each metric as is. For example: source 1: [1, None, 2] source 2: [3, None, 4, 5] before this change becomes: source 1: [1, 1, 2, 2] source 2: [3, 3, 4, 5] and after this change: source 1: [1, 1, 2] source 2: [3, 3, 4, 5] --- scripts/osg_stats.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index d898accb10..e42d62452a 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -149,17 +149,18 @@ def collect_per_frame(sources, keys, begin_frame, end_frame, frame_number_name): for key in keys: result[name][key] = [None] * (end_frame - begin_frame) for name, frames in sources.items(): + max_index = 0 for frame in frames: number = frame[frame_number_name] if begin_frame <= number < end_frame: index = number - begin_frame + max_index = max(max_index, index) for key in keys: if key in frame: result[name][key][index] = frame[key] - for name in result.keys(): for key in keys: prev = 0.0 - values = result[name][key] + values = result[name][key][:max_index + 1] for i in range(len(values)): if values[i] is not None: prev = values[i] @@ -183,9 +184,11 @@ def draw_timeseries(sources, keys, add_sum, begin_frame, end_frame): x = numpy.array(range(begin_frame, end_frame)) for name, frames in sources.items(): for key in keys: - ax.plot(x, frames[key], label=f'{key}:{name}') + y = frames[key] + ax.plot(x[:len(y)], y, label=f'{key}:{name}') if add_sum: - ax.plot(x, numpy.sum(list(frames[k] for k in keys), axis=0), label=f'sum:{name}', linestyle='--') + y = numpy.sum(list(frames[k] for k in keys), axis=0) + ax.plot(x[:len(y)], y, label=f'sum:{name}', linestyle='--') ax.grid(True) ax.legend() fig.canvas.manager.set_window_title('timeseries') @@ -196,10 +199,11 @@ def draw_commulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): x = numpy.array(range(begin_frame, end_frame)) for name, frames in sources.items(): for key in keys: - ax.plot(x, numpy.cumsum(frames[key]), label=f'{key}:{name}') + y = numpy.cumsum(frames[key]) + ax.plot(x[:len(y)], y, label=f'{key}:{name}') if add_sum: - ax.plot(x, numpy.cumsum(numpy.sum(list(frames[k] for k in keys), axis=0)), label=f'sum:{name}', - linestyle='--') + y = numpy.cumsum(numpy.sum(list(frames[k] for k in keys), axis=0)) + ax.plot(x[:len(y)], y, label=f'sum:{name}', linestyle='--') ax.grid(True) ax.legend() fig.canvas.manager.set_window_title('commulative_timeseries') @@ -210,10 +214,11 @@ def draw_timeseries_delta(sources, keys, add_sum, begin_frame, end_frame): x = numpy.array(range(begin_frame + 1, end_frame)) for name, frames in sources.items(): for key in keys: - ax.plot(x, numpy.diff(frames[key]), label=f'{key}:{name}') + y = numpy.diff(frames[key]) + ax.plot(x[:len(y)], numpy.diff(frames[key]), label=f'{key}:{name}') if add_sum: - ax.plot(x, numpy.diff(numpy.sum(list(frames[k] for k in keys), axis=0)), label=f'sum:{name}', - linestyle='--') + y = numpy.diff(numpy.sum(list(frames[k] for k in keys), axis=0)) + ax.plot(x[:len(y)], y, label=f'sum:{name}', linestyle='--') ax.grid(True) ax.legend() fig.canvas.manager.set_window_title('timeseries_delta') @@ -312,12 +317,7 @@ def print_stats(sources, keys, stats_sum, precision, sort_by, table_format): style=termtables.styles.markdown, ) elif table_format == 'json': - table = list() - for row in stats: - row_table = dict() - for key, value in zip(metrics, row.values()): - row_table[key] = value - table.append(row_table) + table = [dict(zip(metrics, row.values())) for row in stats] print(json.dumps(table)) else: print(f'Unsupported table format: {table_format}') From 6b860caa3e2b0db7c5fd6fec1a55cb465099f8c4 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 18 Mar 2024 01:26:43 +0100 Subject: [PATCH 277/451] Fix spelling --- scripts/osg_stats.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index e42d62452a..20fae2cac8 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -23,11 +23,11 @@ import termtables help='Print a list of all present keys in the input file.') @click.option('--regexp_match', is_flag=True, help='Use all metric that match given key. ' - 'Can be used with stats, timeseries, commulative_timeseries, hist, hist_threshold') + 'Can be used with stats, timeseries, cumulative_timeseries, hist, hist_threshold') @click.option('--timeseries', type=str, multiple=True, help='Show a graph for given metric over time.') -@click.option('--commulative_timeseries', type=str, multiple=True, - help='Show a graph for commulative sum of a given metric over time.') +@click.option('--cumulative_timeseries', type=str, multiple=True, + help='Show a graph for cumulative sum of a given metric over time.') @click.option('--timeseries_delta', type=str, multiple=True, help='Show a graph for delta between neighbouring frames of a given metric over time.') @click.option('--hist', type=str, multiple=True, @@ -54,8 +54,8 @@ import termtables help='Format floating point numbers with given precision') @click.option('--timeseries_sum', is_flag=True, help='Add a graph to timeseries for a sum per frame of all given timeseries metrics.') -@click.option('--commulative_timeseries_sum', is_flag=True, - help='Add a graph to timeseries for a sum per frame of all given commulative timeseries.') +@click.option('--cumulative_timeseries_sum', is_flag=True, + help='Add a graph to timeseries for a sum per frame of all given cumulative timeseries.') @click.option('--timeseries_delta_sum', is_flag=True, help='Add a graph to timeseries for a sum per frame of all given timeseries delta.') @click.option('--begin_frame', type=int, default=0, @@ -75,7 +75,7 @@ import termtables @click.argument('path', type=click.Path(), nargs=-1) def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plot, stats, precision, timeseries_sum, stats_sum, begin_frame, end_frame, path, - commulative_timeseries, commulative_timeseries_sum, frame_number_name, + cumulative_timeseries, cumulative_timeseries_sum, frame_number_name, hist_threshold, threshold_name, threshold_value, show_common_path_prefix, stats_sort_by, timeseries_delta, timeseries_delta_sum, stats_table_format): sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))} @@ -97,8 +97,8 @@ def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plo if timeseries: draw_timeseries(sources=frames, keys=matching_keys(timeseries), add_sum=timeseries_sum, begin_frame=begin_frame, end_frame=end_frame) - if commulative_timeseries: - draw_commulative_timeseries(sources=frames, keys=matching_keys(commulative_timeseries), add_sum=commulative_timeseries_sum, + if cumulative_timeseries: + draw_cumulative_timeseries(sources=frames, keys=matching_keys(cumulative_timeseries), add_sum=cumulative_timeseries_sum, begin_frame=begin_frame, end_frame=end_frame) if timeseries_delta: draw_timeseries_delta(sources=frames, keys=matching_keys(timeseries_delta), add_sum=timeseries_delta_sum, @@ -194,7 +194,7 @@ def draw_timeseries(sources, keys, add_sum, begin_frame, end_frame): fig.canvas.manager.set_window_title('timeseries') -def draw_commulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): +def draw_cumulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): fig, ax = matplotlib.pyplot.subplots() x = numpy.array(range(begin_frame, end_frame)) for name, frames in sources.items(): @@ -206,7 +206,7 @@ def draw_commulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): ax.plot(x[:len(y)], y, label=f'sum:{name}', linestyle='--') ax.grid(True) ax.legend() - fig.canvas.manager.set_window_title('commulative_timeseries') + fig.canvas.manager.set_window_title('cumulative_timeseries') def draw_timeseries_delta(sources, keys, add_sum, begin_frame, end_frame): From 6b93479bd3a9412ba9ba2ea9c6974cd5451a7750 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 18 Mar 2024 03:21:57 +0300 Subject: [PATCH 278/451] Get rid of ESM4::SubRecordTypes All my homies hate ESM4::SubRecordTypes --- components/esm4/common.hpp | 681 ----------------------------------- components/esm4/loadachr.cpp | 94 ++--- components/esm4/loadacti.cpp | 86 ++--- components/esm4/loadalch.cpp | 72 ++-- components/esm4/loadaloc.cpp | 34 +- components/esm4/loadammo.cpp | 74 ++-- components/esm4/loadanio.cpp | 18 +- components/esm4/loadappa.cpp | 22 +- components/esm4/loadarma.cpp | 92 ++--- components/esm4/loadarmo.cpp | 140 +++---- components/esm4/loadaspc.cpp | 18 +- components/esm4/loadbook.cpp | 70 ++-- components/esm4/loadbptd.cpp | 48 +-- components/esm4/loadcell.cpp | 80 ++-- components/esm4/loadclas.cpp | 14 +- components/esm4/loadclfm.cpp | 10 +- components/esm4/loadclot.cpp | 42 +-- components/esm4/loadcont.cpp | 70 ++-- components/esm4/loadcrea.cpp | 88 ++--- components/esm4/loaddial.cpp | 30 +- components/esm4/loaddobj.cpp | 6 +- components/esm4/loaddoor.cpp | 62 ++-- components/esm4/loadeyes.cpp | 8 +- components/esm4/loadflor.cpp | 62 ++-- components/esm4/loadflst.cpp | 6 +- components/esm4/loadfurn.cpp | 106 +++--- components/esm4/loadglob.cpp | 6 +- components/esm4/loadgmst.cpp | 4 +- components/esm4/loadgras.cpp | 18 +- components/esm4/loadhair.cpp | 14 +- components/esm4/loadhdpt.cpp | 32 +- components/esm4/loadidle.cpp | 30 +- components/esm4/loadidlm.cpp | 28 +- components/esm4/loadimod.cpp | 48 +-- components/esm4/loadinfo.cpp | 116 +++--- components/esm4/loadingr.cpp | 62 ++-- components/esm4/loadkeym.cpp | 56 +-- components/esm4/loadland.cpp | 18 +- components/esm4/loadlgtm.cpp | 6 +- components/esm4/loadligh.cpp | 64 ++-- components/esm4/loadltex.cpp | 16 +- components/esm4/loadlvlc.cpp | 14 +- components/esm4/loadlvli.cpp | 26 +- components/esm4/loadlvln.cpp | 30 +- components/esm4/loadmato.cpp | 16 +- components/esm4/loadmisc.cpp | 64 ++-- components/esm4/loadmset.cpp | 58 +-- components/esm4/loadmstt.cpp | 50 +-- components/esm4/loadmusc.cpp | 12 +- components/esm4/loadnavi.cpp | 12 +- components/esm4/loadnavm.cpp | 28 +- components/esm4/loadnote.cpp | 42 +-- components/esm4/loadnpc.cpp | 232 ++++++------ components/esm4/loadotft.cpp | 4 +- components/esm4/loadpack.cpp | 110 +++--- components/esm4/loadpgrd.cpp | 12 +- components/esm4/loadpgre.cpp | 86 ++--- components/esm4/loadpwat.cpp | 8 +- components/esm4/loadqust.cpp | 144 ++++---- components/esm4/loadrace.cpp | 280 +++++++------- components/esm4/loadrefr.cpp | 214 +++++------ components/esm4/loadregn.cpp | 42 +-- components/esm4/loadroad.cpp | 4 +- components/esm4/loadsbsp.cpp | 4 +- components/esm4/loadscol.cpp | 24 +- components/esm4/loadscpt.cpp | 16 +- components/esm4/loadscrl.cpp | 38 +- components/esm4/loadsgst.cpp | 22 +- components/esm4/loadslgm.cpp | 28 +- components/esm4/loadsndr.cpp | 36 +- components/esm4/loadsoun.cpp | 22 +- components/esm4/loadstat.cpp | 36 +- components/esm4/loadtact.cpp | 48 +-- components/esm4/loadterm.cpp | 94 ++--- components/esm4/loadtes4.cpp | 22 +- components/esm4/loadtree.cpp | 30 +- components/esm4/loadtxst.cpp | 26 +- components/esm4/loadweap.cpp | 206 +++++------ components/esm4/loadwrld.cpp | 80 ++-- components/esm4/reader.cpp | 2 +- components/esm4/reader.hpp | 2 +- 81 files changed, 2047 insertions(+), 2728 deletions(-) diff --git a/components/esm4/common.hpp b/components/esm4/common.hpp index 8f37bfb8d4..a50151b10a 100644 --- a/components/esm4/common.hpp +++ b/components/esm4/common.hpp @@ -176,687 +176,6 @@ namespace ESM4 REC_MSET = fourCC("MSET") // Media Set }; - enum SubRecordTypes - { - SUB_ACBS = fourCC("ACBS"), - SUB_ACEC = fourCC("ACEC"), // TES5 Dawnguard - SUB_ACEP = fourCC("ACEP"), // TES5 Dawnguard - SUB_ACID = fourCC("ACID"), // TES5 Dawnguard - SUB_ACPR = fourCC("ACPR"), // TES5 - SUB_ACSR = fourCC("ACSR"), // TES5 Dawnguard - SUB_ACTV = fourCC("ACTV"), // FO4 - SUB_ACUN = fourCC("ACUN"), // TES5 Dawnguard - SUB_AHCF = fourCC("AHCF"), - SUB_AHCM = fourCC("AHCM"), - SUB_AIDT = fourCC("AIDT"), - SUB_ALCA = fourCC("ALCA"), // TES5 - SUB_ALCC = fourCC("ALCC"), // FO4 - SUB_ALCL = fourCC("ALCL"), // TES5 - SUB_ALCO = fourCC("ALCO"), // TES5 - SUB_ALCS = fourCC("ALCS"), // FO4 - SUB_ALDI = fourCC("ALDI"), // FO4 - SUB_ALDN = fourCC("ALDN"), // TES5 - SUB_ALEA = fourCC("ALEA"), // TES5 - SUB_ALED = fourCC("ALED"), // TES5 - SUB_ALEQ = fourCC("ALEQ"), // TES5 - SUB_ALFA = fourCC("ALFA"), // TES5 - SUB_ALFC = fourCC("ALFC"), // TES5 - SUB_ALFD = fourCC("ALFD"), // TES5 - SUB_ALFE = fourCC("ALFE"), // TES5 - SUB_ALFI = fourCC("ALFI"), // TES5 - SUB_ALFL = fourCC("ALFL"), // TES5 - SUB_ALFR = fourCC("ALFR"), // TES5 - SUB_ALFV = fourCC("ALFV"), // FO4 - SUB_ALID = fourCC("ALID"), // TES5 - SUB_ALLA = fourCC("ALLA"), // FO4 - SUB_ALLS = fourCC("ALLS"), // TES5 - SUB_ALMI = fourCC("ALMI"), // FO4 - SUB_ALNA = fourCC("ALNA"), // TES5 - SUB_ALNT = fourCC("ALNT"), // TES5 - SUB_ALPC = fourCC("ALPC"), // TES5 - SUB_ALRT = fourCC("ALRT"), // TES5 - SUB_ALSP = fourCC("ALSP"), // TES5 - SUB_ALST = fourCC("ALST"), // TES5 - SUB_ALUA = fourCC("ALUA"), // TES5 - SUB_ANAM = fourCC("ANAM"), - SUB_AOR2 = fourCC("AOR2"), // FO4 - SUB_APPR = fourCC("APPR"), // FO4 - SUB_ATKD = fourCC("ATKD"), - SUB_ATKE = fourCC("ATKE"), - SUB_ATKR = fourCC("ATKR"), - SUB_ATKS = fourCC("ATKS"), // FO4 - SUB_ATKT = fourCC("ATKT"), // FO4 - SUB_ATKW = fourCC("ATKW"), // FO4 - SUB_ATTN = fourCC("ATTN"), // FO4 - SUB_ATTR = fourCC("ATTR"), - SUB_ATTX = fourCC("ATTX"), // FO4 - SUB_ATXT = fourCC("ATXT"), - SUB_AVFL = fourCC("AVFL"), // FO4 - SUB_AVSK = fourCC("AVSK"), // TES5 - SUB_BAMT = fourCC("BAMT"), - SUB_BCLF = fourCC("BCLF"), // FO4 - SUB_BIDS = fourCC("BIDS"), - SUB_BIPL = fourCC("BIPL"), // FO3 - SUB_BMCT = fourCC("BMCT"), - SUB_BMDT = fourCC("BMDT"), - SUB_BMMP = fourCC("BMMP"), // FO4 - SUB_BNAM = fourCC("BNAM"), - SUB_BOD2 = fourCC("BOD2"), - SUB_BODT = fourCC("BODT"), - SUB_BPND = fourCC("BPND"), - SUB_BPNI = fourCC("BPNI"), - SUB_BPNN = fourCC("BPNN"), - SUB_BPNT = fourCC("BPNT"), - SUB_BPTN = fourCC("BPTN"), - SUB_BRUS = fourCC("BRUS"), // FONV - SUB_BSIZ = fourCC("BSIZ"), // FO4 - SUB_BSMB = fourCC("BSMB"), // FO4 - SUB_BSMP = fourCC("BSMP"), // FO4 - SUB_BSMS = fourCC("BSMS"), // FO4 - SUB_BTXT = fourCC("BTXT"), - SUB_CDIX = fourCC("CDIX"), // FO4 - SUB_CIS1 = fourCC("CIS1"), // TES5 - SUB_CIS2 = fourCC("CIS2"), // TES5 - SUB_CITC = fourCC("CITC"), // TES5 - SUB_CLSZ = fourCC("CLSZ"), // FO4 - SUB_CNAM = fourCC("CNAM"), - SUB_CNTO = fourCC("CNTO"), - SUB_COCT = fourCC("COCT"), - SUB_COED = fourCC("COED"), - SUB_CRDT = fourCC("CRDT"), - SUB_CRGR = fourCC("CRGR"), // TES5 - SUB_CRIF = fourCC("CRIF"), - SUB_CRIS = fourCC("CRIS"), // FO4 - SUB_CRVA = fourCC("CRVA"), // TES5 - SUB_CS2D = fourCC("CS2D"), // FO4 - SUB_CS2E = fourCC("CS2E"), // FO4 - SUB_CS2F = fourCC("CS2F"), // FO4 - SUB_CS2H = fourCC("CS2H"), // FO4 - SUB_CS2K = fourCC("CS2K"), // FO4 - SUB_CSCR = fourCC("CSCR"), - SUB_CSCV = fourCC("CSCV"), // FO4 - SUB_CSDC = fourCC("CSDC"), - SUB_CSDI = fourCC("CSDI"), - SUB_CSDT = fourCC("CSDT"), - SUB_CSFL = fourCC("CSFL"), // TES5 - SUB_CSGD = fourCC("CSGD"), // TES5 - SUB_CSLR = fourCC("CSLR"), // TES5 - SUB_CSMD = fourCC("CSMD"), // TES5 - SUB_CSME = fourCC("CSME"), // TES5 - SUB_CSRA = fourCC("CSRA"), // FO4 - SUB_CTDA = fourCC("CTDA"), - SUB_CTDT = fourCC("CTDT"), - SUB_CUSD = fourCC("CUSD"), // FO4 - SUB_CVPA = fourCC("CVPA"), // FO4 - SUB_DALC = fourCC("DALC"), // FO3 - SUB_DAMA = fourCC("DAMA"), // FO4 - SUB_DAMC = fourCC("DAMC"), // FO4 - SUB_DAT2 = fourCC("DAT2"), // FONV - SUB_DATA = fourCC("DATA"), - SUB_DELE = fourCC("DELE"), - SUB_DEMO = fourCC("DEMO"), // TES5 - SUB_DESC = fourCC("DESC"), - SUB_DEST = fourCC("DEST"), - SUB_DEVA = fourCC("DEVA"), // TES5 - SUB_DFTF = fourCC("DFTF"), - SUB_DFTM = fourCC("DFTM"), - SUB_DMAX = fourCC("DMAX"), // TES5 - SUB_DMDC = fourCC("DMDC"), // FO4 - SUB_DMDL = fourCC("DMDL"), - SUB_DMDS = fourCC("DMDS"), - SUB_DMDT = fourCC("DMDT"), - SUB_DMIN = fourCC("DMIN"), // TES5 - SUB_DNAM = fourCC("DNAM"), - SUB_DODT = fourCC("DODT"), - SUB_DOFT = fourCC("DOFT"), - SUB_DPLT = fourCC("DPLT"), - SUB_DSTA = fourCC("DSTA"), // FO4 - SUB_DSTD = fourCC("DSTD"), - SUB_DSTF = fourCC("DSTF"), - SUB_DTGT = fourCC("DTGT"), // FO4 - SUB_DTID = fourCC("DTID"), // FO4 - SUB_EAMT = fourCC("EAMT"), - SUB_ECOR = fourCC("ECOR"), - SUB_EDID = fourCC("EDID"), - SUB_EFID = fourCC("EFID"), - SUB_EFIT = fourCC("EFIT"), - SUB_EFSD = fourCC("EFSD"), // FONV DeadMoney - SUB_EITM = fourCC("EITM"), - SUB_ENAM = fourCC("ENAM"), - SUB_ENIT = fourCC("ENIT"), - SUB_EPF2 = fourCC("EPF2"), - SUB_EPF3 = fourCC("EPF3"), - SUB_EPFB = fourCC("EPFB"), // FO4 - SUB_EPFD = fourCC("EPFD"), - SUB_EPFT = fourCC("EPFT"), - SUB_ESCE = fourCC("ESCE"), - SUB_ETYP = fourCC("ETYP"), - SUB_FCHT = fourCC("FCHT"), // TES5 - SUB_FCPL = fourCC("FCPL"), // FO4 - SUB_FFFF = fourCC("FFFF"), - SUB_FGGA = fourCC("FGGA"), - SUB_FGGS = fourCC("FGGS"), - SUB_FGTS = fourCC("FGTS"), - SUB_FIMD = fourCC("FIMD"), // FO4 - SUB_FLMV = fourCC("FLMV"), - SUB_FLTR = fourCC("FLTR"), // TES5 - SUB_FLTV = fourCC("FLTV"), - SUB_FMIN = fourCC("FMIN"), // FO4 - SUB_FMRI = fourCC("FMRI"), // FO4 - SUB_FMRN = fourCC("FMRN"), // FO4 - SUB_FMRS = fourCC("FMRS"), // FO4 - SUB_FNAM = fourCC("FNAM"), - SUB_FNMK = fourCC("FNMK"), - SUB_FNPR = fourCC("FNPR"), - SUB_FPRT = fourCC("FPRT"), // TES5 - SUB_FTSF = fourCC("FTSF"), - SUB_FTSM = fourCC("FTSM"), - SUB_FTST = fourCC("FTST"), - SUB_FTYP = fourCC("FTYP"), // FO4 - SUB_FULL = fourCC("FULL"), - SUB_FVPA = fourCC("FVPA"), // FO4 - SUB_GNAM = fourCC("GNAM"), - SUB_GREE = fourCC("GREE"), // FO4 - SUB_GWOR = fourCC("GWOR"), // TES5 - SUB_HCLF = fourCC("HCLF"), - SUB_HCLR = fourCC("HCLR"), - SUB_HEAD = fourCC("HEAD"), - SUB_HEDR = fourCC("HEDR"), - SUB_HLTX = fourCC("HLTX"), // FO4 - SUB_HNAM = fourCC("HNAM"), - SUB_HTID = fourCC("HTID"), // TES5 - SUB_ICO2 = fourCC("ICO2"), - SUB_ICON = fourCC("ICON"), - SUB_IDLA = fourCC("IDLA"), - SUB_IDLB = fourCC("IDLB"), // FO3 - SUB_IDLC = fourCC("IDLC"), - SUB_IDLF = fourCC("IDLF"), - SUB_IDLT = fourCC("IDLT"), - SUB_IMPF = fourCC("IMPF"), // FO3 Anchorage - SUB_IMPS = fourCC("IMPS"), // FO3 Anchorage - SUB_IMSP = fourCC("IMSP"), // TES5 - SUB_INAM = fourCC("INAM"), - SUB_INCC = fourCC("INCC"), - SUB_INDX = fourCC("INDX"), - SUB_INFC = fourCC("INFC"), // FONV - SUB_INFX = fourCC("INFX"), // FONV - SUB_INRD = fourCC("INRD"), // FO4 - SUB_INTT = fourCC("INTT"), // FO4 - SUB_INTV = fourCC("INTV"), - SUB_IOVR = fourCC("IOVR"), // FO4 - SUB_ISIZ = fourCC("ISIZ"), // FO4 - SUB_ITID = fourCC("ITID"), // FO4 - SUB_ITMC = fourCC("ITMC"), // FO4 - SUB_ITME = fourCC("ITME"), // FO4 - SUB_ITMS = fourCC("ITMS"), // FO4 - SUB_ITXT = fourCC("ITXT"), - SUB_JAIL = fourCC("JAIL"), // TES5 - SUB_JNAM = fourCC("JNAM"), // FONV - SUB_JOUT = fourCC("JOUT"), // TES5 - SUB_KFFZ = fourCC("KFFZ"), - SUB_KNAM = fourCC("KNAM"), - SUB_KSIZ = fourCC("KSIZ"), - SUB_KWDA = fourCC("KWDA"), - SUB_LCEC = fourCC("LCEC"), // TES5 - SUB_LCEP = fourCC("LCEP"), // TES5 - SUB_LCID = fourCC("LCID"), // TES5 - SUB_LCPR = fourCC("LCPR"), // TES5 - SUB_LCSR = fourCC("LCSR"), // TES5 - SUB_LCUN = fourCC("LCUN"), // TES5 - SUB_LFSD = fourCC("LFSD"), // FO4 - SUB_LFSP = fourCC("LFSP"), // FO4 - SUB_LLCT = fourCC("LLCT"), - SUB_LLKC = fourCC("LLKC"), // FO4 - SUB_LNAM = fourCC("LNAM"), - SUB_LTMP = fourCC("LTMP"), - SUB_LTPC = fourCC("LTPC"), // FO4 - SUB_LTPT = fourCC("LTPT"), // FO4 - SUB_LVLD = fourCC("LVLD"), - SUB_LVLF = fourCC("LVLF"), - SUB_LVLG = fourCC("LVLG"), // FO3 - SUB_LVLM = fourCC("LVLM"), // FO4 - SUB_LVLO = fourCC("LVLO"), - SUB_LVSG = fourCC("LVSG"), // FO4 - SUB_MASE = fourCC("MASE"), // FO4 - SUB_MAST = fourCC("MAST"), - SUB_MCHT = fourCC("MCHT"), // TES5 - SUB_MDOB = fourCC("MDOB"), - SUB_MHDT = fourCC("MHDT"), - SUB_MIC2 = fourCC("MIC2"), - SUB_MICO = fourCC("MICO"), - SUB_MLSI = fourCC("MLSI"), // FO4 - SUB_MMRK = fourCC("MMRK"), // FONV - SUB_MNAM = fourCC("MNAM"), - SUB_MO2B = fourCC("MO2B"), - SUB_MO2C = fourCC("MO2C"), // FO4 - SUB_MO2F = fourCC("MO2F"), // FO4 - SUB_MO2S = fourCC("MO2S"), - SUB_MO2T = fourCC("MO2T"), - SUB_MO3B = fourCC("MO3B"), - SUB_MO3C = fourCC("MO3C"), // FO4 - SUB_MO3F = fourCC("MO3F"), // FO4 - SUB_MO3S = fourCC("MO3S"), // FO3 - SUB_MO3T = fourCC("MO3T"), - SUB_MO4B = fourCC("MO4B"), - SUB_MO4C = fourCC("MO4C"), // FO4 - SUB_MO4F = fourCC("MO4F"), // FO4 - SUB_MO4S = fourCC("MO4S"), - SUB_MO4T = fourCC("MO4T"), - SUB_MO5C = fourCC("MO5C"), // FO4 - SUB_MO5F = fourCC("MO5F"), // FO4 - SUB_MO5S = fourCC("MO5S"), // TES5 - SUB_MO5T = fourCC("MO5T"), - SUB_MOD2 = fourCC("MOD2"), - SUB_MOD3 = fourCC("MOD3"), - SUB_MOD4 = fourCC("MOD4"), - SUB_MOD5 = fourCC("MOD5"), - SUB_MODB = fourCC("MODB"), - SUB_MODC = fourCC("MODC"), // FO4 - SUB_MODD = fourCC("MODD"), // FO3 - SUB_MODF = fourCC("MODF"), // FO4 - SUB_MODL = fourCC("MODL"), - SUB_MODQ = fourCC("MODQ"), // FO4 - SUB_MODS = fourCC("MODS"), - SUB_MODT = fourCC("MODT"), - SUB_MOSD = fourCC("MOSD"), // FO3 - SUB_MPAI = fourCC("MPAI"), - SUB_MPAV = fourCC("MPAV"), - SUB_MPCD = fourCC("MPCD"), // FO4 - SUB_MPGN = fourCC("MPGN"), // FO4 - SUB_MPGS = fourCC("MPGS"), // FO4 - SUB_MPPC = fourCC("MPPC"), // FO4 - SUB_MPPF = fourCC("MPPF"), // FO4 - SUB_MPPI = fourCC("MPPI"), // FO4 - SUB_MPPK = fourCC("MPPK"), // FO4 - SUB_MPPM = fourCC("MPPM"), // FO4 - SUB_MPPN = fourCC("MPPN"), // FO4 - SUB_MPPT = fourCC("MPPT"), // FO4 - SUB_MPRT = fourCC("MPRT"), // TES5 - SUB_MRSV = fourCC("MRSV"), // FO4 - SUB_MSDK = fourCC("MSDK"), // FO4 - SUB_MSDV = fourCC("MSDV"), // FO4 - SUB_MSID = fourCC("MSID"), // FO4 - SUB_MSM0 = fourCC("MSM0"), // FO4 - SUB_MSM1 = fourCC("MSM1"), // FO4 - SUB_MTNM = fourCC("MTNM"), - SUB_MTYP = fourCC("MTYP"), - SUB_MWD1 = fourCC("MWD1"), // FONV - SUB_MWD2 = fourCC("MWD2"), // FONV - SUB_MWD3 = fourCC("MWD3"), // FONV - SUB_MWD4 = fourCC("MWD4"), // FONV - SUB_MWD5 = fourCC("MWD5"), // FONV - SUB_MWD6 = fourCC("MWD6"), // FONV - SUB_MWD7 = fourCC("MWD7"), // FONV - SUB_MWGT = fourCC("MWGT"), // FO4 - SUB_NAM0 = fourCC("NAM0"), - SUB_NAM1 = fourCC("NAM1"), - SUB_NAM2 = fourCC("NAM2"), - SUB_NAM3 = fourCC("NAM3"), - SUB_NAM4 = fourCC("NAM4"), - SUB_NAM5 = fourCC("NAM5"), - SUB_NAM6 = fourCC("NAM6"), - SUB_NAM7 = fourCC("NAM7"), - SUB_NAM8 = fourCC("NAM8"), - SUB_NAM9 = fourCC("NAM9"), - SUB_NAMA = fourCC("NAMA"), - SUB_NAME = fourCC("NAME"), - SUB_NETO = fourCC("NETO"), // FO4 - SUB_NEXT = fourCC("NEXT"), // FO3 - SUB_NIFT = fourCC("NIFT"), - SUB_NIFZ = fourCC("NIFZ"), - SUB_NNAM = fourCC("NNAM"), - SUB_NNGS = fourCC("NNGS"), // FO4 - SUB_NNGT = fourCC("NNGT"), // FO4 - SUB_NNUS = fourCC("NNUS"), // FO4 - SUB_NNUT = fourCC("NNUT"), // FO4 - SUB_NONE = fourCC("NONE"), // FO4 - SUB_NPOS = fourCC("NPOS"), // FO4 - SUB_NPOT = fourCC("NPOT"), // FO4 - SUB_NQUS = fourCC("NQUS"), // FO4 - SUB_NQUT = fourCC("NQUT"), // FO4 - SUB_NTOP = fourCC("NTOP"), // FO4 - SUB_NTRM = fourCC("NTRM"), // FO4 - SUB_NULL = fourCC("NULL"), - SUB_NVCA = fourCC("NVCA"), // FO3 - SUB_NVCI = fourCC("NVCI"), // FO3 - SUB_NVDP = fourCC("NVDP"), // FO3 - SUB_NVER = fourCC("NVER"), - SUB_NVEX = fourCC("NVEX"), // FO3 - SUB_NVGD = fourCC("NVGD"), // FO3 - SUB_NVMI = fourCC("NVMI"), - SUB_NVNM = fourCC("NVNM"), - SUB_NVPP = fourCC("NVPP"), - SUB_NVSI = fourCC("NVSI"), - SUB_NVTR = fourCC("NVTR"), // FO3 - SUB_NVVX = fourCC("NVVX"), // FO3 - SUB_OBND = fourCC("OBND"), - SUB_OBTE = fourCC("OBTE"), // FO4 - SUB_OBTF = fourCC("OBTF"), // FO4 - SUB_OBTS = fourCC("OBTS"), // FO4 - SUB_OCOR = fourCC("OCOR"), // TES5 - SUB_OFST = fourCC("OFST"), // TES4 only? - SUB_ONAM = fourCC("ONAM"), - SUB_PCMB = fourCC("PCMB"), // FO4 - SUB_PDTO = fourCC("PDTO"), - SUB_PFIG = fourCC("PFIG"), - SUB_PFO2 = fourCC("PFO2"), // TES5 - SUB_PFOR = fourCC("PFOR"), // TES5 - SUB_PFPC = fourCC("PFPC"), - SUB_PFRN = fourCC("PFRN"), // FO4 - SUB_PGAG = fourCC("PGAG"), - SUB_PGRI = fourCC("PGRI"), - SUB_PGRL = fourCC("PGRL"), - SUB_PGRP = fourCC("PGRP"), - SUB_PGRR = fourCC("PGRR"), - SUB_PHTN = fourCC("PHTN"), - SUB_PHWT = fourCC("PHWT"), - SUB_PKAM = fourCC("PKAM"), // FO3 - SUB_PKC2 = fourCC("PKC2"), // TES5 - SUB_PKCU = fourCC("PKCU"), // TES5 - SUB_PKD2 = fourCC("PKD2"), // FO3 - SUB_PKDD = fourCC("PKDD"), // FO3 - SUB_PKDT = fourCC("PKDT"), - SUB_PKE2 = fourCC("PKE2"), // FO3 - SUB_PKED = fourCC("PKED"), // FO3 - SUB_PKFD = fourCC("PKFD"), // FO3 - SUB_PKID = fourCC("PKID"), - SUB_PKPT = fourCC("PKPT"), // FO3 - SUB_PKW3 = fourCC("PKW3"), // FO3 - SUB_PLCN = fourCC("PLCN"), // TES5 - SUB_PLD2 = fourCC("PLD2"), // FO3 - SUB_PLDT = fourCC("PLDT"), - SUB_PLVD = fourCC("PLVD"), // TES5 - SUB_PNAM = fourCC("PNAM"), - SUB_POBA = fourCC("POBA"), // FO3 - SUB_POCA = fourCC("POCA"), // FO3 - SUB_POEA = fourCC("POEA"), // FO3 - SUB_PRCB = fourCC("PRCB"), // TES5 - SUB_PRKC = fourCC("PRKC"), - SUB_PRKE = fourCC("PRKE"), - SUB_PRKF = fourCC("PRKF"), - SUB_PRKR = fourCC("PRKR"), - SUB_PRKZ = fourCC("PRKZ"), - SUB_PRPS = fourCC("PRPS"), // FO4 - SUB_PSDT = fourCC("PSDT"), - SUB_PTD2 = fourCC("PTD2"), // FO3 - SUB_PTDA = fourCC("PTDA"), // TES5 - SUB_PTDT = fourCC("PTDT"), - SUB_PTOP = fourCC("PTOP"), // FO4 - SUB_PTRN = fourCC("PTRN"), // FO4 - SUB_PUID = fourCC("PUID"), // FO3 - SUB_QNAM = fourCC("QNAM"), - SUB_QOBJ = fourCC("QOBJ"), // FO3 - SUB_QSDT = fourCC("QSDT"), - SUB_QSTA = fourCC("QSTA"), - SUB_QSTI = fourCC("QSTI"), - SUB_QSTR = fourCC("QSTR"), - SUB_QTGL = fourCC("QTGL"), // TES5 - SUB_QTOP = fourCC("QTOP"), // FO4 - SUB_QUAL = fourCC("QUAL"), - SUB_RADR = fourCC("RADR"), // FO4 - SUB_RAGA = fourCC("RAGA"), - SUB_RBPC = fourCC("RBPC"), // FO4 - SUB_RCEC = fourCC("RCEC"), // TES5 - SUB_RCIL = fourCC("RCIL"), // FONV - SUB_RCLR = fourCC("RCLR"), - SUB_RCPR = fourCC("RCPR"), // TES5 Dawnguard - SUB_RCSR = fourCC("RCSR"), // TES5 - SUB_RCUN = fourCC("RCUN"), // TES5 - SUB_RDAT = fourCC("RDAT"), - SUB_RDGS = fourCC("RDGS"), - SUB_RDID = fourCC("RDID"), // FONV - SUB_RDMD = fourCC("RDMD"), // TES4 only? - SUB_RDMO = fourCC("RDMO"), - SUB_RDMP = fourCC("RDMP"), - SUB_RDOT = fourCC("RDOT"), - SUB_RDSA = fourCC("RDSA"), - SUB_RDSB = fourCC("RDSB"), // FONV - SUB_RDSD = fourCC("RDSD"), // TES4 only? - SUB_RDSI = fourCC("RDSI"), // FONV - SUB_RDWT = fourCC("RDWT"), - SUB_REPL = fourCC("REPL"), // FO3 - SUB_REPT = fourCC("REPT"), // FO4 - SUB_RLDM = fourCC("RLDM"), // FO4 - SUB_RNAM = fourCC("RNAM"), - SUB_RNMV = fourCC("RNMV"), - SUB_RPLD = fourCC("RPLD"), - SUB_RPLI = fourCC("RPLI"), - SUB_RPRF = fourCC("RPRF"), - SUB_RPRM = fourCC("RPRM"), - SUB_RVIS = fourCC("RVIS"), // FO4 - SUB_SADD = fourCC("SADD"), // FO4 - SUB_SAKD = fourCC("SAKD"), // FO4 - SUB_SAPT = fourCC("SAPT"), // FO4 - SUB_SCDA = fourCC("SCDA"), - SUB_SCHD = fourCC("SCHD"), - SUB_SCHR = fourCC("SCHR"), - SUB_SCIT = fourCC("SCIT"), - SUB_SCQS = fourCC("SCQS"), // FO4 - SUB_SCRI = fourCC("SCRI"), - SUB_SCRN = fourCC("SCRN"), - SUB_SCRO = fourCC("SCRO"), - SUB_SCRV = fourCC("SCRV"), // FONV - SUB_SCTX = fourCC("SCTX"), - SUB_SCVR = fourCC("SCVR"), // FONV - SUB_SDSC = fourCC("SDSC"), - SUB_SGNM = fourCC("SGNM"), // FO4 - SUB_SHRT = fourCC("SHRT"), - SUB_SLCP = fourCC("SLCP"), - SUB_SLSD = fourCC("SLSD"), // FONV - SUB_SNAM = fourCC("SNAM"), - SUB_SNDD = fourCC("SNDD"), - SUB_SNDX = fourCC("SNDX"), - SUB_SNMV = fourCC("SNMV"), - SUB_SOFT = fourCC("SOFT"), - SUB_SOUL = fourCC("SOUL"), - SUB_SPCT = fourCC("SPCT"), - SUB_SPED = fourCC("SPED"), - SUB_SPIT = fourCC("SPIT"), - SUB_SPLO = fourCC("SPLO"), - SUB_SPMV = fourCC("SPMV"), // TES5 - SUB_SPOR = fourCC("SPOR"), - SUB_SRAC = fourCC("SRAC"), // FO4 - SUB_SRAF = fourCC("SRAF"), // FO4 - SUB_SSPN = fourCC("SSPN"), // FO4 - SUB_STCP = fourCC("STCP"), // FO4 - SUB_STKD = fourCC("STKD"), // FO4 - SUB_STOL = fourCC("STOL"), // TES5 - SUB_STOP = fourCC("STOP"), // FO4 - SUB_STSC = fourCC("STSC"), // FO4 - SUB_SWMV = fourCC("SWMV"), - SUB_TCFU = fourCC("TCFU"), // FONV - SUB_TCLF = fourCC("TCLF"), - SUB_TCLT = fourCC("TCLT"), - SUB_TDUM = fourCC("TDUM"), // FONV - SUB_TEND = fourCC("TEND"), // FO4 - SUB_TETI = fourCC("TETI"), // FO4 - SUB_TIAS = fourCC("TIAS"), - SUB_TIFC = fourCC("TIFC"), // TES5 - SUB_TINC = fourCC("TINC"), - SUB_TIND = fourCC("TIND"), - SUB_TINI = fourCC("TINI"), - SUB_TINL = fourCC("TINL"), - SUB_TINP = fourCC("TINP"), - SUB_TINT = fourCC("TINT"), - SUB_TINV = fourCC("TINV"), - SUB_TIQS = fourCC("TIQS"), // FO4 - SUB_TIRS = fourCC("TIRS"), - SUB_TNAM = fourCC("TNAM"), - SUB_TPIC = fourCC("TPIC"), - SUB_TPLT = fourCC("TPLT"), - SUB_TPTA = fourCC("TPTA"), // FO4 - SUB_TRDA = fourCC("TRDA"), // FO4 - SUB_TRDT = fourCC("TRDT"), - SUB_TSCE = fourCC("TSCE"), // FO4 - SUB_TTEB = fourCC("TTEB"), // FO4 - SUB_TTEC = fourCC("TTEC"), // FO4 - SUB_TTED = fourCC("TTED"), // FO4 - SUB_TTEF = fourCC("TTEF"), // FO4 - SUB_TTET = fourCC("TTET"), // FO4 - SUB_TTGE = fourCC("TTGE"), // FO4 - SUB_TTGP = fourCC("TTGP"), // FO4 - SUB_TVDT = fourCC("TVDT"), - SUB_TWAT = fourCC("TWAT"), // TES5 - SUB_TX00 = fourCC("TX00"), - SUB_TX01 = fourCC("TX01"), - SUB_TX02 = fourCC("TX02"), - SUB_TX03 = fourCC("TX03"), - SUB_TX04 = fourCC("TX04"), - SUB_TX05 = fourCC("TX05"), - SUB_TX06 = fourCC("TX06"), - SUB_TX07 = fourCC("TX07"), - SUB_UNAM = fourCC("UNAM"), - SUB_UNES = fourCC("UNES"), - SUB_UNWP = fourCC("UNWP"), // FO4 - SUB_VANM = fourCC("VANM"), // FONV - SUB_VATS = fourCC("VATS"), // FONV - SUB_VCLR = fourCC("VCLR"), - SUB_VENC = fourCC("VENC"), // TES5 - SUB_VEND = fourCC("VEND"), // TES5 - SUB_VENV = fourCC("VENV"), // TES5 - SUB_VHGT = fourCC("VHGT"), - SUB_VISI = fourCC("VISI"), // FO4 - SUB_VMAD = fourCC("VMAD"), - SUB_VNAM = fourCC("VNAM"), - SUB_VNML = fourCC("VNML"), - SUB_VTCK = fourCC("VTCK"), - SUB_VTEX = fourCC("VTEX"), - SUB_VTXT = fourCC("VTXT"), - SUB_WAIT = fourCC("WAIT"), // TES5 - SUB_WAMD = fourCC("WAMD"), // FO4 - SUB_WBDT = fourCC("WBDT"), - SUB_WCTR = fourCC("WCTR"), - SUB_WGDR = fourCC("WGDR"), // FO4 - SUB_WKMV = fourCC("WKMV"), - SUB_WLEV = fourCC("WLEV"), // FO4 - SUB_WLST = fourCC("WLST"), - SUB_WMAP = fourCC("WMAP"), // FO4 - SUB_WMI1 = fourCC("WMI1"), // FONV - SUB_WMI2 = fourCC("WMI2"), // FONV - SUB_WMI3 = fourCC("WMI3"), // FONV - SUB_WMS1 = fourCC("WMS1"), // FONV - SUB_WMS2 = fourCC("WMS2"), // FONV - SUB_WNAM = fourCC("WNAM"), - SUB_WNM1 = fourCC("WNM1"), // FONV - SUB_WNM2 = fourCC("WNM2"), // FONV - SUB_WNM3 = fourCC("WNM3"), // FONV - SUB_WNM4 = fourCC("WNM4"), // FONV - SUB_WNM5 = fourCC("WNM5"), // FONV - SUB_WNM6 = fourCC("WNM6"), // FONV - SUB_WNM7 = fourCC("WNM7"), // FONV - SUB_WZMD = fourCC("WZMD"), // FO4 - SUB_XACT = fourCC("XACT"), - SUB_XALP = fourCC("XALP"), - SUB_XAMC = fourCC("XAMC"), // FO3 - SUB_XAMT = fourCC("XAMT"), // FO3 - SUB_XAPD = fourCC("XAPD"), - SUB_XAPR = fourCC("XAPR"), - SUB_XASP = fourCC("XASP"), // FO4 - SUB_XATO = fourCC("XATO"), // FONV - SUB_XATP = fourCC("XATP"), // FO4 - SUB_XATR = fourCC("XATR"), - SUB_XBSD = fourCC("XBSD"), // FO4 - SUB_XCAS = fourCC("XCAS"), - SUB_XCCM = fourCC("XCCM"), - SUB_XCCP = fourCC("XCCP"), - SUB_XCET = fourCC("XCET"), // FO3 - SUB_XCGD = fourCC("XCGD"), - SUB_XCHG = fourCC("XCHG"), // thievery.exp - SUB_XCIM = fourCC("XCIM"), - SUB_XCLC = fourCC("XCLC"), - SUB_XCLL = fourCC("XCLL"), - SUB_XCLP = fourCC("XCLP"), // FO3 - SUB_XCLR = fourCC("XCLR"), - SUB_XCLW = fourCC("XCLW"), - SUB_XCMO = fourCC("XCMO"), - SUB_XCMT = fourCC("XCMT"), // TES4 only? - SUB_XCNT = fourCC("XCNT"), - SUB_XCRI = fourCC("XCRI"), // FO4 - SUB_XCVL = fourCC("XCVL"), - SUB_XCVR = fourCC("XCVR"), - SUB_XCWT = fourCC("XCWT"), - SUB_XCZA = fourCC("XCZA"), - SUB_XCZC = fourCC("XCZC"), - SUB_XCZR = fourCC("XCZR"), // TES5 - SUB_XDCR = fourCC("XDCR"), // FO3 - SUB_XEMI = fourCC("XEMI"), - SUB_XESP = fourCC("XESP"), - SUB_XEZN = fourCC("XEZN"), - SUB_XFVC = fourCC("XFVC"), - SUB_XGDR = fourCC("XGDR"), // FO4 - SUB_XGLB = fourCC("XGLB"), - SUB_XHLP = fourCC("XHLP"), // FO3 - SUB_XHLT = fourCC("XHLT"), // Unofficial Oblivion Patch - SUB_XHOR = fourCC("XHOR"), - SUB_XHRS = fourCC("XHRS"), - SUB_XHTW = fourCC("XHTW"), - SUB_XIBS = fourCC("XIBS"), // FO3 - SUB_XILL = fourCC("XILL"), - SUB_XILW = fourCC("XILW"), // FO4 - SUB_XIS2 = fourCC("XIS2"), - SUB_XLCM = fourCC("XLCM"), - SUB_XLCN = fourCC("XLCN"), - SUB_XLIB = fourCC("XLIB"), - SUB_XLIG = fourCC("XLIG"), - SUB_XLKR = fourCC("XLKR"), - SUB_XLKT = fourCC("XLKT"), // FO4 - SUB_XLOC = fourCC("XLOC"), - SUB_XLOD = fourCC("XLOD"), - SUB_XLRL = fourCC("XLRL"), - SUB_XLRM = fourCC("XLRM"), - SUB_XLRT = fourCC("XLRT"), - SUB_XLTW = fourCC("XLTW"), - SUB_XLYR = fourCC("XLYR"), // FO4 - SUB_XMBO = fourCC("XMBO"), - SUB_XMBP = fourCC("XMBP"), - SUB_XMBR = fourCC("XMBR"), - SUB_XMRC = fourCC("XMRC"), - SUB_XMRK = fourCC("XMRK"), - SUB_XMSP = fourCC("XMSP"), // FO4 - SUB_XNAM = fourCC("XNAM"), - SUB_XNDP = fourCC("XNDP"), - SUB_XOCP = fourCC("XOCP"), - SUB_XORD = fourCC("XORD"), // FO3 - SUB_XOWN = fourCC("XOWN"), - SUB_XPCI = fourCC("XPCI"), - SUB_XPDD = fourCC("XPDD"), // FO4 - SUB_XPLK = fourCC("XPLK"), // FO4 - SUB_XPOD = fourCC("XPOD"), - SUB_XPPA = fourCC("XPPA"), - SUB_XPRD = fourCC("XPRD"), - SUB_XPRI = fourCC("XPRI"), // FO4 - SUB_XPRM = fourCC("XPRM"), - SUB_XPTL = fourCC("XPTL"), - SUB_XPWR = fourCC("XPWR"), - SUB_XRAD = fourCC("XRAD"), // FO3 - SUB_XRDO = fourCC("XRDO"), // FO3 - SUB_XRDS = fourCC("XRDS"), - SUB_XRFG = fourCC("XRFG"), // FO4 - SUB_XRGB = fourCC("XRGB"), - SUB_XRGD = fourCC("XRGD"), - SUB_XRMR = fourCC("XRMR"), - SUB_XRNK = fourCC("XRNK"), // TES4 only? - SUB_XRTM = fourCC("XRTM"), - SUB_XSCL = fourCC("XSCL"), - SUB_XSED = fourCC("XSED"), - SUB_XSPC = fourCC("XSPC"), - SUB_XSRD = fourCC("XSRD"), // FONV - SUB_XSRF = fourCC("XSRF"), // FONV - SUB_XTEL = fourCC("XTEL"), - SUB_XTNM = fourCC("XTNM"), - SUB_XTRG = fourCC("XTRG"), - SUB_XTRI = fourCC("XTRI"), - SUB_XWCN = fourCC("XWCN"), - SUB_XWCS = fourCC("XWCS"), - SUB_XWCU = fourCC("XWCU"), - SUB_XWEM = fourCC("XWEM"), - SUB_XWPG = fourCC("XWPG"), // FO4 - SUB_XWPN = fourCC("XWPN"), // FO4 - SUB_XXXX = fourCC("XXXX"), - SUB_YNAM = fourCC("YNAM"), - SUB_ZNAM = fourCC("ZNAM"), - }; - // Based on http://www.uesp.net/wiki/Tes5Mod:Mod_File_Format#Records enum RecordFlag { diff --git a/components/esm4/loadachr.cpp b/components/esm4/loadachr.cpp index dc181dda4b..6ccdc0a8c7 100644 --- a/components/esm4/loadachr.cpp +++ b/components/esm4/loadachr.cpp @@ -42,22 +42,22 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_NAME: + case ESM::fourCC("NAME"): reader.getFormId(mBaseObj); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mPos); break; - case ESM4::SUB_XSCL: + case ESM::fourCC("XSCL"): reader.get(mScale); break; - case ESM4::SUB_XOWN: + case ESM::fourCC("XOWN"): { switch (subHdr.dataSize) { @@ -78,57 +78,57 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XESP: + case ESM::fourCC("XESP"): reader.getFormId(mEsp.parent); reader.get(mEsp.flags); break; - case ESM4::SUB_XCNT: + case ESM::fourCC("XCNT"): { reader.get(mCount); break; } - case ESM4::SUB_XRGD: // ragdoll - case ESM4::SUB_XRGB: // ragdoll biped - case ESM4::SUB_XHRS: // horse formId - case ESM4::SUB_XMRC: // merchant container formId + case ESM::fourCC("XRGD"): // ragdoll + case ESM::fourCC("XRGB"): // ragdoll biped + case ESM::fourCC("XHRS"): // horse formId + case ESM::fourCC("XMRC"): // merchant container formId // TES5 - case ESM4::SUB_XAPD: // activation parent - case ESM4::SUB_XAPR: // active parent - case ESM4::SUB_XEZN: // encounter zone - case ESM4::SUB_XHOR: - case ESM4::SUB_XLCM: // levelled creature - case ESM4::SUB_XLCN: // location - case ESM4::SUB_XLKR: // location route? - case ESM4::SUB_XLRT: // location type + case ESM::fourCC("XAPD"): // activation parent + case ESM::fourCC("XAPR"): // active parent + case ESM::fourCC("XEZN"): // encounter zone + case ESM::fourCC("XHOR"): + case ESM::fourCC("XLCM"): // levelled creature + case ESM::fourCC("XLCN"): // location + case ESM::fourCC("XLKR"): // location route? + case ESM::fourCC("XLRT"): // location type // - case ESM4::SUB_XPRD: - case ESM4::SUB_XPPA: - case ESM4::SUB_INAM: - case ESM4::SUB_PDTO: + case ESM::fourCC("XPRD"): + case ESM::fourCC("XPPA"): + case ESM::fourCC("INAM"): + case ESM::fourCC("PDTO"): // - case ESM4::SUB_XIS2: - case ESM4::SUB_XPCI: // formId - case ESM4::SUB_XLOD: - case ESM4::SUB_VMAD: - case ESM4::SUB_XLRL: // Unofficial Skyrim Patch - case ESM4::SUB_XRDS: // FO3 - case ESM4::SUB_XIBS: // FO3 - case ESM4::SUB_SCHR: // FO3 - case ESM4::SUB_TNAM: // FO3 - case ESM4::SUB_XATO: // FONV - case ESM4::SUB_MNAM: // FO4 - case ESM4::SUB_XATP: // FO4 - case ESM4::SUB_XEMI: // FO4 - case ESM4::SUB_XFVC: // FO4 - case ESM4::SUB_XHLT: // FO4 - case ESM4::SUB_XHTW: // FO4 - case ESM4::SUB_XLKT: // FO4 - case ESM4::SUB_XLYR: // FO4 - case ESM4::SUB_XMBR: // FO4 - case ESM4::SUB_XMSP: // FO4 - case ESM4::SUB_XPLK: // FO4 - case ESM4::SUB_XRFG: // FO4 - case ESM4::SUB_XRNK: // FO4 + case ESM::fourCC("XIS2"): + case ESM::fourCC("XPCI"): // formId + case ESM::fourCC("XLOD"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("XLRL"): // Unofficial Skyrim Patch + case ESM::fourCC("XRDS"): // FO3 + case ESM::fourCC("XIBS"): // FO3 + case ESM::fourCC("SCHR"): // FO3 + case ESM::fourCC("TNAM"): // FO3 + case ESM::fourCC("XATO"): // FONV + case ESM::fourCC("MNAM"): // FO4 + case ESM::fourCC("XATP"): // FO4 + case ESM::fourCC("XEMI"): // FO4 + case ESM::fourCC("XFVC"): // FO4 + case ESM::fourCC("XHLT"): // FO4 + case ESM::fourCC("XHTW"): // FO4 + case ESM::fourCC("XLKT"): // FO4 + case ESM::fourCC("XLYR"): // FO4 + case ESM::fourCC("XMBR"): // FO4 + case ESM::fourCC("XMSP"): // FO4 + case ESM::fourCC("XPLK"): // FO4 + case ESM::fourCC("XRFG"): // FO4 + case ESM::fourCC("XRNK"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadacti.cpp b/components/esm4/loadacti.cpp index 74eaff2dab..0609e4e1bf 100644 --- a/components/esm4/loadacti.cpp +++ b/components/esm4/loadacti.cpp @@ -41,69 +41,69 @@ void ESM4::Activator::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mLoopingSound); break; - case ESM4::SUB_VNAM: + case ESM::fourCC("VNAM"): reader.getFormId(mActivationSound); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mRadioTemplate); break; // FONV - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mRadioStation); break; - case ESM4::SUB_XATO: + case ESM::fourCC("XATO"): reader.getZString(mActivationPrompt); break; // FONV - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_FNAM: - case ESM4::SUB_KNAM: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_OBND: - case ESM4::SUB_PNAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_WNAM: - case ESM4::SUB_CTDA: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_CITC: - case ESM4::SUB_NVNM: - case ESM4::SUB_ATTX: // FO4 - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_RADR: // FO4 - case ESM4::SUB_STCP: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("FNAM"): + case ESM::fourCC("KNAM"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("WNAM"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("CITC"): + case ESM::fourCC("NVNM"): + case ESM::fourCC("ATTX"): // FO4 + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("RADR"): // FO4 + case ESM::fourCC("STCP"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadalch.cpp b/components/esm4/loadalch.cpp index 1ecfda25e8..4a289ab760 100644 --- a/components/esm4/loadalch.cpp +++ b/components/esm4/loadalch.cpp @@ -42,35 +42,35 @@ void ESM4::Potion::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_SCIT: + case ESM::fourCC("SCIT"): reader.get(mEffect); reader.adjustFormId(mEffect.formId); break; - case ESM4::SUB_ENIT: + case ESM::fourCC("ENIT"): if (subHdr.dataSize == 8) // TES4 { reader.get(&mItem, 8); @@ -82,36 +82,36 @@ void ESM4::Potion::load(ESM4::Reader& reader) reader.adjustFormId(mItem.withdrawl); reader.adjustFormId(mItem.sound); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_EFID: - case ESM4::SUB_EFIT: - case ESM4::SUB_CTDA: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_OBND: - case ESM4::SUB_ETYP: // FO3 - case ESM4::SUB_DESC: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_CUSD: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("EFID"): + case ESM::fourCC("EFIT"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("ETYP"): // FO3 + case ESM::fourCC("DESC"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("CUSD"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadaloc.cpp b/components/esm4/loadaloc.cpp index 690684df7c..1b3bfd7e5b 100644 --- a/components/esm4/loadaloc.cpp +++ b/components/esm4/loadaloc.cpp @@ -42,34 +42,34 @@ void ESM4::MediaLocationController::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_GNAM: + case ESM::fourCC("GNAM"): reader.getFormId(mBattleSets.emplace_back()); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.getFormId(mLocationSets.emplace_back()); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mEnemySets.emplace_back()); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): reader.getFormId(mNeutralSets.emplace_back()); break; - case ESM4::SUB_XNAM: + case ESM::fourCC("XNAM"): reader.getFormId(mFriendSets.emplace_back()); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mAllySets.emplace_back()); break; - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mConditionalFaction); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): { reader.get(mMediaFlags); std::uint8_t flags = mMediaFlags.loopingOptions; @@ -77,21 +77,21 @@ void ESM4::MediaLocationController::load(ESM4::Reader& reader) mMediaFlags.factionNotFound = flags & 0x0F; // WARN: overwriting data break; } - case ESM4::SUB_NAM4: + case ESM::fourCC("NAM4"): reader.get(mLocationDelay); break; - case ESM4::SUB_NAM7: + case ESM::fourCC("NAM7"): reader.get(mRetriggerDelay); break; - case ESM4::SUB_NAM5: + case ESM::fourCC("NAM5"): reader.get(mDayStart); break; - case ESM4::SUB_NAM6: + case ESM::fourCC("NAM6"): reader.get(mNightStart); break; - case ESM4::SUB_NAM2: // always 0? 4 bytes - case ESM4::SUB_NAM3: // always 0? 4 bytes - case ESM4::SUB_FNAM: // always 0? 4 bytes + case ESM::fourCC("NAM2"): // always 0? 4 bytes + case ESM::fourCC("NAM3"): // always 0? 4 bytes + case ESM::fourCC("FNAM"): // always 0? 4 bytes { #if 0 std::vector mDataBuf(subHdr.dataSize); diff --git a/components/esm4/loadammo.cpp b/components/esm4/loadammo.cpp index 8c5bc45c85..39c42fc83f 100644 --- a/components/esm4/loadammo.cpp +++ b/components/esm4/loadammo.cpp @@ -41,13 +41,13 @@ void ESM4::Ammunition::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): switch (subHdr.dataSize) { case 18: // TES4 @@ -86,7 +86,7 @@ void ESM4::Ammunition::load(ESM4::Reader& reader) break; } break; - case ESM4::SUB_DAT2: + case ESM::fourCC("DAT2"): if (subHdr.dataSize == 20) { reader.get(mData.mProjPerShot); @@ -100,71 +100,71 @@ void ESM4::Ammunition::load(ESM4::Reader& reader) reader.skipSubRecordData(); } break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.getFormId(mData.mProjectile); reader.get(mData.mFlags); mData.mFlags &= 0xFF; reader.get(mData.mDamage); reader.get(mData.mHealth); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_ONAM: + case ESM::fourCC("ONAM"): reader.getLocalizedString(mShortName); break; - case ESM4::SUB_QNAM: // FONV + case ESM::fourCC("QNAM"): // FONV reader.getLocalizedString(mAbbrev); break; - case ESM4::SUB_RCIL: + case ESM::fourCC("RCIL"): reader.getFormId(mAmmoEffects.emplace_back()); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScript); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_NAM1: // FO4 casing model data - case ESM4::SUB_NAM2: // + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("NAM1"): // FO4 casing model data + case ESM::fourCC("NAM2"): // reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadanio.cpp b/components/esm4/loadanio.cpp index fa440f5ace..a8156eef2b 100644 --- a/components/esm4/loadanio.cpp +++ b/components/esm4/loadanio.cpp @@ -41,25 +41,25 @@ void ESM4::AnimObject::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): reader.getZString(mUnloadEvent); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.getFormId(mIdleAnim); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadappa.cpp b/components/esm4/loadappa.cpp index 45e12739b9..8c74d020a6 100644 --- a/components/esm4/loadappa.cpp +++ b/components/esm4/loadappa.cpp @@ -41,13 +41,13 @@ void ESM4::Apparatus::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) { reader.get(mData.value); @@ -61,24 +61,24 @@ void ESM4::Apparatus::load(ESM4::Reader& reader) reader.get(mData.quality); } break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_MODT: - case ESM4::SUB_OBND: - case ESM4::SUB_QUAL: + case ESM::fourCC("MODT"): + case ESM::fourCC("OBND"): + case ESM::fourCC("QUAL"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadarma.cpp b/components/esm4/loadarma.cpp index 2bb6240ee8..a1a1a10845 100644 --- a/components/esm4/loadarma.cpp +++ b/components/esm4/loadarma.cpp @@ -43,39 +43,39 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MOD2: + case ESM::fourCC("MOD2"): reader.getZString(mModelMale); break; - case ESM4::SUB_MOD3: + case ESM::fourCC("MOD3"): reader.getZString(mModelFemale); break; - case ESM4::SUB_MOD4: - case ESM4::SUB_MOD5: + case ESM::fourCC("MOD4"): + case ESM::fourCC("MOD5"): { std::string model; reader.getZString(model); break; } - case ESM4::SUB_NAM0: + case ESM::fourCC("NAM0"): reader.getFormId(mTextureMale); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.getFormId(mTextureFemale); break; - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mRacePrimary); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): if ((esmVer == ESM::VER_094 || esmVer == ESM::VER_170) && subHdr.dataSize == 4) // TES5 reader.getFormId(mRaces.emplace_back()); else reader.skipSubRecordData(); // FIXME: this should be mModelMale for FO3/FONV break; - case ESM4::SUB_BODT: // body template + case ESM::fourCC("BODT"): // body template reader.get(mBodyTemplate.bodyPart); reader.get(mBodyTemplate.flags); reader.get(mBodyTemplate.unknown1); // probably padding @@ -83,7 +83,7 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader) reader.get(mBodyTemplate.unknown3); // probably padding reader.get(mBodyTemplate.type); break; - case ESM4::SUB_BOD2: // TES5+ + case ESM::fourCC("BOD2"): // TES5+ reader.get(mBodyTemplate.bodyPart); mBodyTemplate.flags = 0; mBodyTemplate.unknown1 = 0; // probably padding @@ -94,7 +94,7 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader) reader.get(mBodyTemplate.type); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): if (subHdr.dataSize == 12) { std::uint16_t unknownInt16; @@ -111,40 +111,40 @@ void ESM4::ArmorAddon::load(ESM4::Reader& reader) else reader.skipSubRecordData(); break; - case ESM4::SUB_MO2T: // FIXME: should group with MOD2 - case ESM4::SUB_MO2S: // FIXME: should group with MOD2 - case ESM4::SUB_MO2C: // FIXME: should group with MOD2 - case ESM4::SUB_MO2F: // FIXME: should group with MOD2 - case ESM4::SUB_MO3T: // FIXME: should group with MOD3 - case ESM4::SUB_MO3S: // FIXME: should group with MOD3 - case ESM4::SUB_MO3C: // FIXME: should group with MOD3 - case ESM4::SUB_MO3F: // FIXME: should group with MOD3 - case ESM4::SUB_MOSD: // FO3 // FIXME: should group with MOD3 - case ESM4::SUB_MO4T: // FIXME: should group with MOD4 - case ESM4::SUB_MO4S: // FIXME: should group with MOD4 - case ESM4::SUB_MO4C: // FIXME: should group with MOD4 - case ESM4::SUB_MO4F: // FIXME: should group with MOD4 - case ESM4::SUB_MO5T: - case ESM4::SUB_MO5S: - case ESM4::SUB_MO5C: - case ESM4::SUB_MO5F: - case ESM4::SUB_NAM2: // txst formid male - case ESM4::SUB_NAM3: // txst formid female - case ESM4::SUB_SNDD: // footset sound formid - case ESM4::SUB_BMDT: // FO3 - case ESM4::SUB_DATA: // FO3 - case ESM4::SUB_ETYP: // FO3 - case ESM4::SUB_FULL: // FO3 - case ESM4::SUB_ICO2: // FO3 // female - case ESM4::SUB_ICON: // FO3 // male - case ESM4::SUB_MODT: // FO3 // FIXME: should group with MODL - case ESM4::SUB_MODS: // FO3 // FIXME: should group with MODL - case ESM4::SUB_MODD: // FO3 // FIXME: should group with MODL - case ESM4::SUB_OBND: // FO3 - case ESM4::SUB_BSMB: // FO4 - case ESM4::SUB_BSMP: // FO4 - case ESM4::SUB_BSMS: // FO4 - case ESM4::SUB_ONAM: // FO4 + case ESM::fourCC("MO2T"): // FIXME: should group with MOD2 + case ESM::fourCC("MO2S"): // FIXME: should group with MOD2 + case ESM::fourCC("MO2C"): // FIXME: should group with MOD2 + case ESM::fourCC("MO2F"): // FIXME: should group with MOD2 + case ESM::fourCC("MO3T"): // FIXME: should group with MOD3 + case ESM::fourCC("MO3S"): // FIXME: should group with MOD3 + case ESM::fourCC("MO3C"): // FIXME: should group with MOD3 + case ESM::fourCC("MO3F"): // FIXME: should group with MOD3 + case ESM::fourCC("MOSD"): // FO3 // FIXME: should group with MOD3 + case ESM::fourCC("MO4T"): // FIXME: should group with MOD4 + case ESM::fourCC("MO4S"): // FIXME: should group with MOD4 + case ESM::fourCC("MO4C"): // FIXME: should group with MOD4 + case ESM::fourCC("MO4F"): // FIXME: should group with MOD4 + case ESM::fourCC("MO5T"): + case ESM::fourCC("MO5S"): + case ESM::fourCC("MO5C"): + case ESM::fourCC("MO5F"): + case ESM::fourCC("NAM2"): // txst formid male + case ESM::fourCC("NAM3"): // txst formid female + case ESM::fourCC("SNDD"): // footset sound formid + case ESM::fourCC("BMDT"): // FO3 + case ESM::fourCC("DATA"): // FO3 + case ESM::fourCC("ETYP"): // FO3 + case ESM::fourCC("FULL"): // FO3 + case ESM::fourCC("ICO2"): // FO3 // female + case ESM::fourCC("ICON"): // FO3 // male + case ESM::fourCC("MODT"): // FO3 // FIXME: should group with MODL + case ESM::fourCC("MODS"): // FO3 // FIXME: should group with MODL + case ESM::fourCC("MODD"): // FO3 // FIXME: should group with MODL + case ESM::fourCC("OBND"): // FO3 + case ESM::fourCC("BSMB"): // FO4 + case ESM::fourCC("BSMP"): // FO4 + case ESM::fourCC("BSMS"): // FO4 + case ESM::fourCC("ONAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadarmo.cpp b/components/esm4/loadarmo.cpp index 572cbd6ecd..dc926f7a01 100644 --- a/components/esm4/loadarmo.cpp +++ b/components/esm4/loadarmo.cpp @@ -41,13 +41,13 @@ void ESM4::Armor::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { switch (subHdr.dataSize) { @@ -69,12 +69,12 @@ void ESM4::Armor::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_INDX: // FO4 + case ESM::fourCC("INDX"): // FO4 { reader.get(currentIndex); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): { if (subHdr.dataSize == 4) { @@ -97,28 +97,28 @@ void ESM4::Armor::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MOD2: + case ESM::fourCC("MOD2"): reader.getZString(mModelMaleWorld); break; - case ESM4::SUB_MOD3: + case ESM::fourCC("MOD3"): reader.getZString(mModelFemale); break; - case ESM4::SUB_MOD4: + case ESM::fourCC("MOD4"): reader.getZString(mModelFemaleWorld); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIconMale); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIconMale); break; - case ESM4::SUB_ICO2: + case ESM::fourCC("ICO2"): reader.getZString(mIconFemale); break; - case ESM4::SUB_MIC2: + case ESM::fourCC("MIC2"): reader.getZString(mMiniIconFemale); break; - case ESM4::SUB_BMDT: + case ESM::fourCC("BMDT"): if (subHdr.dataSize == 8) // FO3 { reader.get(mArmorFlags); @@ -133,7 +133,7 @@ void ESM4::Armor::load(ESM4::Reader& reader) mGeneralFlags |= TYPE_TES4; } break; - case ESM4::SUB_BODT: + case ESM::fourCC("BODT"): { reader.get(mArmorFlags); uint32_t flags = 0; @@ -146,7 +146,7 @@ void ESM4::Armor::load(ESM4::Reader& reader) mGeneralFlags |= TYPE_TES5; break; } - case ESM4::SUB_BOD2: + case ESM::fourCC("BOD2"): // FO4, TES5 if (subHdr.dataSize == 4 || subHdr.dataSize == 8) { @@ -163,75 +163,75 @@ void ESM4::Armor::load(ESM4::Reader& reader) reader.skipSubRecordData(); } break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: - case ESM4::SUB_MO2B: - case ESM4::SUB_MO3B: - case ESM4::SUB_MO4B: - case ESM4::SUB_MO2T: - case ESM4::SUB_MO2S: - case ESM4::SUB_MO3T: - case ESM4::SUB_MO4T: - case ESM4::SUB_MO4S: - case ESM4::SUB_OBND: - case ESM4::SUB_RNAM: // race formid - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_TNAM: - case ESM4::SUB_DNAM: - case ESM4::SUB_BAMT: - case ESM4::SUB_BIDS: - case ESM4::SUB_ETYP: - case ESM4::SUB_BMCT: - case ESM4::SUB_EAMT: - case ESM4::SUB_EITM: - case ESM4::SUB_VMAD: - case ESM4::SUB_REPL: // FO3 - case ESM4::SUB_BIPL: // FO3 - case ESM4::SUB_MODD: // FO3 - case ESM4::SUB_MOSD: // FO3 - case ESM4::SUB_MODS: // FO3 - case ESM4::SUB_MO3S: // FO3 - case ESM4::SUB_BNAM: // FONV - case ESM4::SUB_SNAM: // FONV - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_DAMA: // FO4 - case ESM4::SUB_FNAM: // FO4 - case ESM4::SUB_INRD: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_OBTE: // FO4 object template start - case ESM4::SUB_OBTF: - case ESM4::SUB_OBTS: - case ESM4::SUB_STOP: // FO4 object template end + case ESM::fourCC("MODT"): + case ESM::fourCC("MO2B"): + case ESM::fourCC("MO3B"): + case ESM::fourCC("MO4B"): + case ESM::fourCC("MO2T"): + case ESM::fourCC("MO2S"): + case ESM::fourCC("MO3T"): + case ESM::fourCC("MO4T"): + case ESM::fourCC("MO4S"): + case ESM::fourCC("OBND"): + case ESM::fourCC("RNAM"): // race formid + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("DNAM"): + case ESM::fourCC("BAMT"): + case ESM::fourCC("BIDS"): + case ESM::fourCC("ETYP"): + case ESM::fourCC("BMCT"): + case ESM::fourCC("EAMT"): + case ESM::fourCC("EITM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("REPL"): // FO3 + case ESM::fourCC("BIPL"): // FO3 + case ESM::fourCC("MODD"): // FO3 + case ESM::fourCC("MOSD"): // FO3 + case ESM::fourCC("MODS"): // FO3 + case ESM::fourCC("MO3S"): // FO3 + case ESM::fourCC("BNAM"): // FONV + case ESM::fourCC("SNAM"): // FONV + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("DAMA"): // FO4 + case ESM::fourCC("FNAM"): // FO4 + case ESM::fourCC("INRD"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("OBTE"): // FO4 object template start + case ESM::fourCC("OBTF"): + case ESM::fourCC("OBTS"): + case ESM::fourCC("STOP"): // FO4 object template end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadaspc.cpp b/components/esm4/loadaspc.cpp index d79df9d8ef..e8fe9d3b34 100644 --- a/components/esm4/loadaspc.cpp +++ b/components/esm4/loadaspc.cpp @@ -41,35 +41,35 @@ void ESM4::AcousticSpace::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnvironmentType); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mAmbientLoopSounds.emplace_back()); break; - case ESM4::SUB_RDAT: + case ESM::fourCC("RDAT"): reader.getFormId(mSoundRegion); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.get(mIsInterior); break; - case ESM4::SUB_XTRI: + case ESM::fourCC("XTRI"): std::uint8_t isInterior; reader.get(isInterior); mIsInterior = isInterior; break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): { // usually 0 for FONV (maybe # of close Actors for Walla to trigger) (4 bytes) // Weather attenuation in FO4 (2 bytes) reader.skipSubRecordData(); break; } - case ESM4::SUB_BNAM: // TES5 reverb formid - case ESM4::SUB_OBND: + case ESM::fourCC("BNAM"): // TES5 reverb formid + case ESM::fourCC("OBND"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadbook.cpp b/components/esm4/loadbook.cpp index f2842e5313..cef942074a 100644 --- a/components/esm4/loadbook.cpp +++ b/components/esm4/loadbook.cpp @@ -42,16 +42,16 @@ void ESM4::Book::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { switch (subHdr.dataSize) { @@ -82,53 +82,53 @@ void ESM4::Book::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_CNAM: - case ESM4::SUB_INAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_FIMD: // FO4 - case ESM4::SUB_MICO: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("CNAM"): + case ESM::fourCC("INAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("FIMD"): // FO4 + case ESM::fourCC("MICO"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadbptd.cpp b/components/esm4/loadbptd.cpp index 509eadfcf1..5ff3b5908b 100644 --- a/components/esm4/loadbptd.cpp +++ b/components/esm4/loadbptd.cpp @@ -56,56 +56,56 @@ void ESM4::BodyPartData::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_BPTN: + case ESM::fourCC("BPTN"): reader.getLocalizedString(bodyPart.mPartName); break; - case ESM4::SUB_BPNN: + case ESM::fourCC("BPNN"): reader.getZString(bodyPart.mNodeName); break; - case ESM4::SUB_BPNT: + case ESM::fourCC("BPNT"): reader.getZString(bodyPart.mVATSTarget); break; - case ESM4::SUB_BPNI: + case ESM::fourCC("BPNI"): reader.getZString(bodyPart.mIKStartNode); break; - case ESM4::SUB_BPND: + case ESM::fourCC("BPND"): if (subHdr.dataSize == sizeof(bodyPart.mData)) reader.get(bodyPart.mData); // FIXME: FO4 else reader.skipSubRecordData(); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.getZString(bodyPart.mLimbReplacementModel); break; - case ESM4::SUB_NAM4: // FIXME: assumed occurs last + case ESM::fourCC("NAM4"): // FIXME: assumed occurs last reader.getZString(bodyPart.mGoreEffectsTarget); // target bone mBodyParts.push_back(bodyPart); // FIXME: possible without copying? bodyPart.clear(); break; - case ESM4::SUB_NAM5: - case ESM4::SUB_RAGA: // ragdoll - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_BNAM: // FO4 - case ESM4::SUB_CNAM: // FO4 - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_ENAM: // FO4 - case ESM4::SUB_FNAM: // FO4 - case ESM4::SUB_INAM: // FO4 - case ESM4::SUB_JNAM: // FO4 - case ESM4::SUB_NAM2: // FO4 + case ESM::fourCC("NAM5"): + case ESM::fourCC("RAGA"): // ragdoll + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("BNAM"): // FO4 + case ESM::fourCC("CNAM"): // FO4 + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("ENAM"): // FO4 + case ESM::fourCC("FNAM"): // FO4 + case ESM::fourCC("INAM"): // FO4 + case ESM::fourCC("JNAM"): // FO4 + case ESM::fourCC("NAM2"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadcell.cpp b/components/esm4/loadcell.cpp index 0091ab0bd6..8106c1637f 100644 --- a/components/esm4/loadcell.cpp +++ b/components/esm4/loadcell.cpp @@ -78,7 +78,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): { if (!reader.getZString(mEditorId)) throw std::runtime_error("CELL EDID data read error"); @@ -89,7 +89,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_XCLC: + case ESM::fourCC("XCLC"): { //(X, Y) grid location of the cell followed by flags. Always in // exterior cells and never in interior cells. @@ -114,10 +114,10 @@ void ESM4::Cell::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize == 2) reader.get(mCellFlags); @@ -131,7 +131,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XCLR: // for exterior cells + case ESM::fourCC("XCLR"): // for exterior cells { mRegions.resize(subHdr.dataSize / sizeof(ESM::FormId32)); for (std::vector::iterator it = mRegions.begin(); it != mRegions.end(); ++it) @@ -145,7 +145,7 @@ void ESM4::Cell::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XOWN: + case ESM::fourCC("XOWN"): { switch (subHdr.dataSize) { @@ -166,19 +166,19 @@ void ESM4::Cell::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XGLB: + case ESM::fourCC("XGLB"): reader.getFormId(mGlobal); break; // Oblivion only? - case ESM4::SUB_XCCM: + case ESM::fourCC("XCCM"): reader.getFormId(mClimate); break; - case ESM4::SUB_XCWT: + case ESM::fourCC("XCWT"): reader.getFormId(mWater); break; - case ESM4::SUB_XCLW: + case ESM::fourCC("XCLW"): reader.get(mWaterHeight); break; - case ESM4::SUB_XCLL: + case ESM::fourCC("XCLL"): { switch (subHdr.dataSize) { @@ -197,45 +197,45 @@ void ESM4::Cell::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XCMT: + case ESM::fourCC("XCMT"): reader.get(mMusicType); break; // Oblivion only? - case ESM4::SUB_LTMP: + case ESM::fourCC("LTMP"): reader.getFormId(mLightingTemplate); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.get(mLightingTemplateFlags); break; // seems to always follow LTMP - case ESM4::SUB_XCMO: + case ESM::fourCC("XCMO"): reader.getFormId(mMusic); break; - case ESM4::SUB_XCAS: + case ESM::fourCC("XCAS"): reader.getFormId(mAcousticSpace); break; - case ESM4::SUB_TVDT: - case ESM4::SUB_MHDT: - case ESM4::SUB_XCGD: - case ESM4::SUB_XNAM: - case ESM4::SUB_XLCN: - case ESM4::SUB_XWCS: - case ESM4::SUB_XWCU: - case ESM4::SUB_XWCN: - case ESM4::SUB_XCIM: - case ESM4::SUB_XEZN: - case ESM4::SUB_XWEM: - case ESM4::SUB_XILL: - case ESM4::SUB_XRNK: - case ESM4::SUB_XCET: // FO3 - case ESM4::SUB_IMPF: // FO3 Zeta - case ESM4::SUB_CNAM: // FO4 - case ESM4::SUB_PCMB: // FO4 - case ESM4::SUB_RVIS: // FO4 - case ESM4::SUB_VISI: // FO4 - case ESM4::SUB_XGDR: // FO4 - case ESM4::SUB_XILW: // FO4 - case ESM4::SUB_XCRI: // FO4 - case ESM4::SUB_XPRI: // FO4 - case ESM4::SUB_ZNAM: // FO4 + case ESM::fourCC("TVDT"): + case ESM::fourCC("MHDT"): + case ESM::fourCC("XCGD"): + case ESM::fourCC("XNAM"): + case ESM::fourCC("XLCN"): + case ESM::fourCC("XWCS"): + case ESM::fourCC("XWCU"): + case ESM::fourCC("XWCN"): + case ESM::fourCC("XCIM"): + case ESM::fourCC("XEZN"): + case ESM::fourCC("XWEM"): + case ESM::fourCC("XILL"): + case ESM::fourCC("XRNK"): + case ESM::fourCC("XCET"): // FO3 + case ESM::fourCC("IMPF"): // FO3 Zeta + case ESM::fourCC("CNAM"): // FO4 + case ESM::fourCC("PCMB"): // FO4 + case ESM::fourCC("RVIS"): // FO4 + case ESM::fourCC("VISI"): // FO4 + case ESM::fourCC("XGDR"): // FO4 + case ESM::fourCC("XILW"): // FO4 + case ESM::fourCC("XCRI"): // FO4 + case ESM::fourCC("XPRI"): // FO4 + case ESM::fourCC("ZNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadclas.cpp b/components/esm4/loadclas.cpp index 7d232a0aa1..e80b36849e 100644 --- a/components/esm4/loadclas.cpp +++ b/components/esm4/loadclas.cpp @@ -41,21 +41,21 @@ void ESM4::Class::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mDesc); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_DATA: - case ESM4::SUB_ATTR: - case ESM4::SUB_PRPS: + case ESM::fourCC("DATA"): + case ESM::fourCC("ATTR"): + case ESM::fourCC("PRPS"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadclfm.cpp b/components/esm4/loadclfm.cpp index bc887cd15c..b7157877c7 100644 --- a/components/esm4/loadclfm.cpp +++ b/components/esm4/loadclfm.cpp @@ -41,22 +41,22 @@ void ESM4::Colour::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.get(mColour.red); reader.get(mColour.green); reader.get(mColour.blue); reader.get(mColour.custom); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mPlayable); break; - case ESM4::SUB_CTDA: + case ESM::fourCC("CTDA"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadclot.cpp b/components/esm4/loadclot.cpp index c67ac3df6b..48999adb18 100644 --- a/components/esm4/loadclot.cpp +++ b/components/esm4/loadclot.cpp @@ -41,55 +41,55 @@ void ESM4::Clothing::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_BMDT: + case ESM::fourCC("BMDT"): reader.get(mClothingFlags); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModelMale); break; - case ESM4::SUB_MOD2: + case ESM::fourCC("MOD2"): reader.getZString(mModelMaleWorld); break; - case ESM4::SUB_MOD3: + case ESM::fourCC("MOD3"): reader.getZString(mModelFemale); break; - case ESM4::SUB_MOD4: + case ESM::fourCC("MOD4"): reader.getZString(mModelFemaleWorld); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIconMale); break; - case ESM4::SUB_ICO2: + case ESM::fourCC("ICO2"): reader.getZString(mIconFemale); break; - case ESM4::SUB_MODT: - case ESM4::SUB_MO2B: - case ESM4::SUB_MO3B: - case ESM4::SUB_MO4B: - case ESM4::SUB_MO2T: - case ESM4::SUB_MO3T: - case ESM4::SUB_MO4T: + case ESM::fourCC("MODT"): + case ESM::fourCC("MO2B"): + case ESM::fourCC("MO3B"): + case ESM::fourCC("MO4B"): + case ESM::fourCC("MO2T"): + case ESM::fourCC("MO3T"): + case ESM::fourCC("MO4T"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadcont.cpp b/components/esm4/loadcont.cpp index d650678093..a41b06cdd8 100644 --- a/components/esm4/loadcont.cpp +++ b/components/esm4/loadcont.cpp @@ -41,17 +41,17 @@ void ESM4::Container::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mDataFlags); reader.get(mWeight); break; - case ESM4::SUB_CNTO: + case ESM::fourCC("CNTO"): { static InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); @@ -59,47 +59,47 @@ void ESM4::Container::load(ESM4::Reader& reader) mInventory.push_back(inv); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mOpenSound); break; - case ESM4::SUB_QNAM: + case ESM::fourCC("QNAM"): reader.getFormId(mCloseSound); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_VMAD: // TES5 only - case ESM4::SUB_OBND: // TES5 only - case ESM4::SUB_COCT: // TES5 only - case ESM4::SUB_COED: // TES5 only - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_ONAM: - case ESM4::SUB_RNAM: // FONV - case ESM4::SUB_TNAM: - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("VMAD"): // TES5 only + case ESM::fourCC("OBND"): // TES5 only + case ESM::fourCC("COCT"): // TES5 only + case ESM::fourCC("COED"): // TES5 only + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("ONAM"): + case ESM::fourCC("RNAM"): // FONV + case ESM::fourCC("TNAM"): + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadcrea.cpp b/components/esm4/loadcrea.cpp index 0c07eb92e3..532bd5acdb 100644 --- a/components/esm4/loadcrea.cpp +++ b/components/esm4/loadcrea.cpp @@ -45,16 +45,16 @@ void ESM4::Creature::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_CNTO: + case ESM::fourCC("CNTO"): { static InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); @@ -62,76 +62,76 @@ void ESM4::Creature::load(ESM4::Reader& reader) mInventory.push_back(inv); break; } - case ESM4::SUB_SPLO: + case ESM::fourCC("SPLO"): reader.getFormId(mSpell.emplace_back()); break; - case ESM4::SUB_PKID: + case ESM::fourCC("PKID"): reader.getFormId(mAIPackages.emplace_back()); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.get(mFaction); reader.adjustFormId(mFaction.faction); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mDeathItem); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_AIDT: + case ESM::fourCC("AIDT"): if (subHdr.dataSize == 20) // FO3 reader.skipSubRecordData(); else reader.get(mAIData); // 12 bytes break; - case ESM4::SUB_ACBS: + case ESM::fourCC("ACBS"): // if (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || mIsFONV) if (subHdr.dataSize == 24) reader.get(mBaseConfig); else reader.get(&mBaseConfig, 16); // TES4 break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): if (subHdr.dataSize == 17) // FO3 reader.skipSubRecordData(); else reader.get(mData); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mCombatStyle); break; - case ESM4::SUB_CSCR: + case ESM::fourCC("CSCR"): reader.getFormId(mSoundBase); break; - case ESM4::SUB_CSDI: + case ESM::fourCC("CSDI"): reader.getFormId(mSound); break; - case ESM4::SUB_CSDC: + case ESM::fourCC("CSDC"): reader.get(mSoundChance); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): reader.get(mBaseScale); break; - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.get(mTurningSpeed); break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): reader.get(mFootWeight); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_NAM0: + case ESM::fourCC("NAM0"): reader.getZString(mBloodSpray); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.getZString(mBloodDecal); break; - case ESM4::SUB_NIFZ: + case ESM::fourCC("NIFZ"): if (!reader.getZeroTerminatedStringArray(mNif)) throw std::runtime_error("CREA NIFZ data read error"); break; - case ESM4::SUB_NIFT: + case ESM::fourCC("NIFT"): { if (subHdr.dataSize != 4) // FIXME: FO3 { @@ -147,33 +147,33 @@ void ESM4::Creature::load(ESM4::Reader& reader) Log(Debug::Verbose) << "CREA NIFT " << mId << ", non-zero " << nift; break; } - case ESM4::SUB_KFFZ: + case ESM::fourCC("KFFZ"): if (!reader.getZeroTerminatedStringArray(mKf)) throw std::runtime_error("CREA KFFZ data read error"); break; - case ESM4::SUB_TPLT: + case ESM::fourCC("TPLT"): reader.getFormId(mBaseTemplate); break; // FO3 - case ESM4::SUB_PNAM: // FO3/FONV/TES5 + case ESM::fourCC("PNAM"): // FO3/FONV/TES5 reader.getFormId(mBodyParts.emplace_back()); break; - case ESM4::SUB_MODT: - case ESM4::SUB_RNAM: - case ESM4::SUB_CSDT: - case ESM4::SUB_OBND: // FO3 - case ESM4::SUB_EAMT: // FO3 - case ESM4::SUB_VTCK: // FO3 - case ESM4::SUB_NAM4: // FO3 - case ESM4::SUB_NAM5: // FO3 - case ESM4::SUB_CNAM: // FO3 - case ESM4::SUB_LNAM: // FO3 - case ESM4::SUB_EITM: // FO3 - case ESM4::SUB_DEST: // FO3 - case ESM4::SUB_DSTD: // FO3 - case ESM4::SUB_DSTF: // FO3 - case ESM4::SUB_DMDL: // FO3 - case ESM4::SUB_DMDT: // FO3 - case ESM4::SUB_COED: // FO3 + case ESM::fourCC("MODT"): + case ESM::fourCC("RNAM"): + case ESM::fourCC("CSDT"): + case ESM::fourCC("OBND"): // FO3 + case ESM::fourCC("EAMT"): // FO3 + case ESM::fourCC("VTCK"): // FO3 + case ESM::fourCC("NAM4"): // FO3 + case ESM::fourCC("NAM5"): // FO3 + case ESM::fourCC("CNAM"): // FO3 + case ESM::fourCC("LNAM"): // FO3 + case ESM::fourCC("EITM"): // FO3 + case ESM::fourCC("DEST"): // FO3 + case ESM::fourCC("DSTD"): // FO3 + case ESM::fourCC("DSTF"): // FO3 + case ESM::fourCC("DMDL"): // FO3 + case ESM::fourCC("DMDT"): // FO3 + case ESM::fourCC("COED"): // FO3 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loaddial.cpp b/components/esm4/loaddial.cpp index 19e1099482..3ed9b79e0a 100644 --- a/components/esm4/loaddial.cpp +++ b/components/esm4/loaddial.cpp @@ -42,19 +42,19 @@ void ESM4::Dialogue::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mTopicName); break; - case ESM4::SUB_QSTI: + case ESM::fourCC("QSTI"): reader.getFormId(mQuests.emplace_back()); break; - case ESM4::SUB_QSTR: // Seems never used in TES4 + case ESM::fourCC("QSTR"): // Seems never used in TES4 reader.getFormId(mQuestsRemoved.emplace_back()); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize == 4) // TES5 { @@ -74,20 +74,20 @@ void ESM4::Dialogue::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mPriority); break; // FO3/FONV - case ESM4::SUB_TDUM: + case ESM::fourCC("TDUM"): reader.getZString(mTextDumb); break; // FONV - case ESM4::SUB_SCRI: - case ESM4::SUB_INFC: // FONV info connection - case ESM4::SUB_INFX: // FONV info index - case ESM4::SUB_QNAM: // TES5 - case ESM4::SUB_BNAM: // TES5 - case ESM4::SUB_SNAM: // TES5 - case ESM4::SUB_TIFC: // TES5 - case ESM4::SUB_KNAM: // FO4 + case ESM::fourCC("SCRI"): + case ESM::fourCC("INFC"): // FONV info connection + case ESM::fourCC("INFX"): // FONV info index + case ESM::fourCC("QNAM"): // TES5 + case ESM::fourCC("BNAM"): // TES5 + case ESM::fourCC("SNAM"): // TES5 + case ESM::fourCC("TIFC"): // TES5 + case ESM::fourCC("KNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loaddobj.cpp b/components/esm4/loaddobj.cpp index 50135fc7a1..9c0c193f81 100644 --- a/components/esm4/loaddobj.cpp +++ b/components/esm4/loaddobj.cpp @@ -44,10 +44,10 @@ void ESM4::DefaultObj::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; // "DefaultObjectManager" - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.getFormId(mData.stimpack); reader.getFormId(mData.superStimpack); reader.getFormId(mData.radX); @@ -87,7 +87,7 @@ void ESM4::DefaultObj::load(ESM4::Reader& reader) reader.getFormId(mData.cateyeMobileEffectNYI); } break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loaddoor.cpp b/components/esm4/loaddoor.cpp index 7fe38b6b7a..10171085c3 100644 --- a/components/esm4/loaddoor.cpp +++ b/components/esm4/loaddoor.cpp @@ -41,57 +41,57 @@ void ESM4::Door::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mOpenSound); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.getFormId(mCloseSound); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): reader.getFormId(mLoopSound); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mDoorFlags); break; - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.getFormId(mRandomTeleport); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_CNAM: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_ONAM: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("CNAM"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("ONAM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadeyes.cpp b/components/esm4/loadeyes.cpp index 28f6d33c6e..7e356889d9 100644 --- a/components/esm4/loadeyes.cpp +++ b/components/esm4/loadeyes.cpp @@ -41,16 +41,16 @@ void ESM4::Eyes::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; default: diff --git a/components/esm4/loadflor.cpp b/components/esm4/loadflor.cpp index 69f1c82b13..164f97eff1 100644 --- a/components/esm4/loadflor.cpp +++ b/components/esm4/loadflor.cpp @@ -41,53 +41,53 @@ void ESM4::Flora::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_PFIG: + case ESM::fourCC("PFIG"): reader.getFormId(mIngredient); break; - case ESM4::SUB_PFPC: + case ESM::fourCC("PFPC"): reader.get(mPercentHarvest); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mSound); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_FNAM: - case ESM4::SUB_OBND: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_PNAM: - case ESM4::SUB_RNAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_ATTX: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("FNAM"): + case ESM::fourCC("OBND"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("RNAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("ATTX"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadflst.cpp b/components/esm4/loadflst.cpp index 9da17bc84b..4acf4d28d2 100644 --- a/components/esm4/loadflst.cpp +++ b/components/esm4/loadflst.cpp @@ -41,13 +41,13 @@ void ESM4::FormIdList::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.getFormId(mObjects.emplace_back()); break; default: diff --git a/components/esm4/loadfurn.cpp b/components/esm4/loadfurn.cpp index 41ddca07a2..75dc3751a6 100644 --- a/components/esm4/loadfurn.cpp +++ b/components/esm4/loadfurn.cpp @@ -41,10 +41,10 @@ void ESM4::Furniture::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): { std::string name; reader.getLocalizedString(name); @@ -53,65 +53,65 @@ void ESM4::Furniture::load(ESM4::Reader& reader) mFullName = std::move(name); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): reader.get(mActiveMarkerFlags); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_ENAM: - case ESM4::SUB_FNAM: - case ESM4::SUB_FNMK: - case ESM4::SUB_FNPR: - case ESM4::SUB_KNAM: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_NAM0: - case ESM4::SUB_OBND: - case ESM4::SUB_PNAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_WBDT: - case ESM4::SUB_XMRK: - case ESM4::SUB_PRPS: - case ESM4::SUB_CTDA: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_ATTX: // FO4 - case ESM4::SUB_CITC: // FO4 - case ESM4::SUB_CNTO: // FO4 - case ESM4::SUB_COCT: // FO4 - case ESM4::SUB_COED: // FO4 - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_NAM1: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_NVNM: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_SNAM: // FO4 - case ESM4::SUB_WNAM: // FO4 - case ESM4::SUB_OBTE: // FO4 object template start - case ESM4::SUB_OBTF: - case ESM4::SUB_OBTS: - case ESM4::SUB_STOP: // FO4 object template end + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("ENAM"): + case ESM::fourCC("FNAM"): + case ESM::fourCC("FNMK"): + case ESM::fourCC("FNPR"): + case ESM::fourCC("KNAM"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("NAM0"): + case ESM::fourCC("OBND"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("WBDT"): + case ESM::fourCC("XMRK"): + case ESM::fourCC("PRPS"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("ATTX"): // FO4 + case ESM::fourCC("CITC"): // FO4 + case ESM::fourCC("CNTO"): // FO4 + case ESM::fourCC("COCT"): // FO4 + case ESM::fourCC("COED"): // FO4 + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("NAM1"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("NVNM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("SNAM"): // FO4 + case ESM::fourCC("WNAM"): // FO4 + case ESM::fourCC("OBTE"): // FO4 object template start + case ESM::fourCC("OBTF"): + case ESM::fourCC("OBTS"): + case ESM::fourCC("STOP"): // FO4 object template end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadglob.cpp b/components/esm4/loadglob.cpp index 436f3e34ae..4349bcb072 100644 --- a/components/esm4/loadglob.cpp +++ b/components/esm4/loadglob.cpp @@ -41,16 +41,16 @@ void ESM4::GlobalVariable::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; case ESM::fourCC("XALG"): // FO76 reader.get(mExtraFlags2); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mType); break; - case ESM4::SUB_FLTV: + case ESM::fourCC("FLTV"): reader.get(mValue); break; case ESM::fourCC("NTWK"): // FO76 diff --git a/components/esm4/loadgmst.cpp b/components/esm4/loadgmst.cpp index f22ed5848d..0b2df075f2 100644 --- a/components/esm4/loadgmst.cpp +++ b/components/esm4/loadgmst.cpp @@ -67,10 +67,10 @@ namespace ESM4 const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): mData = readData(mId, mEditorId, reader); break; default: diff --git a/components/esm4/loadgras.cpp b/components/esm4/loadgras.cpp index ebcdde04a1..8514b3aa0a 100644 --- a/components/esm4/loadgras.cpp +++ b/components/esm4/loadgras.cpp @@ -41,23 +41,23 @@ void ESM4::Grass::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadhair.cpp b/components/esm4/loadhair.cpp index 3ab983d6b6..f3e5a8a1c3 100644 --- a/components/esm4/loadhair.cpp +++ b/components/esm4/loadhair.cpp @@ -41,25 +41,25 @@ void ESM4::Hair::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: + case ESM::fourCC("MODT"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadhdpt.cpp b/components/esm4/loadhdpt.cpp index c560ff5fac..9b7e27bdf9 100644 --- a/components/esm4/loadhdpt.cpp +++ b/components/esm4/loadhdpt.cpp @@ -45,32 +45,32 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; case ESM::fourCC("XALG"): // FO76 reader.get(mExtraFlags2); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): reader.getFormId(mExtraParts.emplace_back()); break; - case ESM4::SUB_NAM0: // TES5 + case ESM::fourCC("NAM0"): // TES5 { std::uint32_t value; reader.get(value); type = value; break; } - case ESM4::SUB_NAM1: // TES5 + case ESM::fourCC("NAM1"): // TES5 { std::string file; reader.getZString(file); @@ -87,29 +87,29 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) mTriFile[*type] = std::move(file); break; } - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.getFormId(mBaseTexture); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getFormId(mColor); break; - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mValidRaces.emplace_back()); break; - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mType); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): case ESM::fourCC("ENLM"): case ESM::fourCC("XFLG"): case ESM::fourCC("ENLT"): case ESM::fourCC("ENLS"): case ESM::fourCC("AUUV"): case ESM::fourCC("MODD"): // Model data end - case ESM4::SUB_CTDA: + case ESM::fourCC("CTDA"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadidle.cpp b/components/esm4/loadidle.cpp index 310c43b2e1..18a408f053 100644 --- a/components/esm4/loadidle.cpp +++ b/components/esm4/loadidle.cpp @@ -41,16 +41,16 @@ void ESM4::IdleAnimation::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.getZString(mCollision); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getZString(mEvent); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): { switch (subHdr.dataSize) { @@ -74,21 +74,21 @@ void ESM4::IdleAnimation::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_CTDA: // formId - case ESM4::SUB_CTDT: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_DATA: - case ESM4::SUB_MODD: - case ESM4::SUB_MODS: - case ESM4::SUB_MODT: - case ESM4::SUB_GNAM: // FO4 + case ESM::fourCC("CTDA"): // formId + case ESM::fourCC("CTDT"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("DATA"): + case ESM::fourCC("MODD"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODT"): + case ESM::fourCC("GNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadidlm.cpp b/components/esm4/loadidlm.cpp index 3f1ed9518c..0aec281c6c 100644 --- a/components/esm4/loadidlm.cpp +++ b/components/esm4/loadidlm.cpp @@ -43,13 +43,13 @@ void ESM4::IdleMarker::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_IDLF: + case ESM::fourCC("IDLF"): reader.get(mIdleFlags); break; - case ESM4::SUB_IDLC: + case ESM::fourCC("IDLC"): if (subHdr.dataSize != 1) // FO3 can have 4? { reader.skipSubRecordData(); @@ -58,10 +58,10 @@ void ESM4::IdleMarker::load(ESM4::Reader& reader) reader.get(mIdleCount); break; - case ESM4::SUB_IDLT: + case ESM::fourCC("IDLT"): reader.get(mIdleTimer); break; - case ESM4::SUB_IDLA: + case ESM::fourCC("IDLA"): { bool isFONV = esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134; if (esmVer == ESM::VER_094 || isFONV) // FO3? 4 or 8 bytes @@ -75,17 +75,17 @@ void ESM4::IdleMarker::load(ESM4::Reader& reader) reader.getFormId(value); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_OBND: // object bounds - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_QNAM: + case ESM::fourCC("OBND"): // object bounds + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("QNAM"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadimod.cpp b/components/esm4/loadimod.cpp index 0359f6d23b..76f51357a3 100644 --- a/components/esm4/loadimod.cpp +++ b/components/esm4/loadimod.cpp @@ -43,53 +43,53 @@ void ESM4::ItemMod::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData.mValue); reader.get(mData.mWeight); break; - case ESM4::SUB_OBND: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODS: - case ESM4::SUB_MODD: // Model data end - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end + case ESM::fourCC("OBND"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODS"): + case ESM::fourCC("MODD"): // Model data end + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadinfo.cpp b/components/esm4/loadinfo.cpp index 1b001c1665..266bdb086c 100644 --- a/components/esm4/loadinfo.cpp +++ b/components/esm4/loadinfo.cpp @@ -49,13 +49,13 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_QSTI: + case ESM::fourCC("QSTI"): reader.getFormId(mQuest); break; // FormId quest id - case ESM4::SUB_SNDD: + case ESM::fourCC("SNDD"): reader.getFormId(mSound); break; // FO3 (not used in FONV?) - case ESM4::SUB_TRDT: + case ESM::fourCC("TRDT"): { if (subHdr.dataSize == 16) // TES4 reader.get(&mResponseData, 16); @@ -70,16 +70,16 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.getLocalizedString(mResponse); break; // response text - case ESM4::SUB_NAM2: + case ESM::fourCC("NAM2"): reader.getZString(mNotes); break; // actor notes - case ESM4::SUB_NAM3: + case ESM::fourCC("NAM3"): reader.getZString(mEdits); break; // not in TES4 - case ESM4::SUB_CTDA: // FIXME: how to detect if 1st/2nd param is a formid? + case ESM::fourCC("CTDA"): // FIXME: how to detect if 1st/2nd param is a formid? { if (subHdr.dataSize == 24) // TES4 reader.get(&mTargetCondition, 24); @@ -105,7 +105,7 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCHR: + case ESM::fourCC("SCHR"): { if (!ignore) reader.get(mScript.scriptHeader); @@ -114,16 +114,16 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCDA: + case ESM::fourCC("SCDA"): reader.skipSubRecordData(); break; // compiled script data - case ESM4::SUB_SCTX: + case ESM::fourCC("SCTX"): reader.getString(mScript.scriptSource); break; - case ESM4::SUB_SCRO: + case ESM::fourCC("SCRO"): reader.getFormId(mScript.globReference); break; - case ESM4::SUB_SLSD: + case ESM::fourCC("SLSD"): { localVar.clear(); reader.get(localVar.index); @@ -136,7 +136,7 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCVR: // assumed always pair with SLSD + case ESM::fourCC("SCVR"): // assumed always pair with SLSD { reader.getZString(localVar.variableName); @@ -144,7 +144,7 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCRV: + case ESM::fourCC("SCRV"): { std::uint32_t index; reader.get(index); @@ -153,13 +153,13 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; } - case ESM4::SUB_NEXT: // FO3/FONV marker for next script header + case ESM::fourCC("NEXT"): // FO3/FONV marker for next script header { ignore = true; break; } - case ESM4::SUB_DATA: // always 3 for TES4 ? + case ESM::fourCC("DATA"): // always 3 for TES4 ? { if (subHdr.dataSize == 4) // FO3/FONV { @@ -171,48 +171,48 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) reader.skipSubRecordData(); // FIXME break; } - case ESM4::SUB_NAME: // FormId add topic (not always present) - case ESM4::SUB_CTDT: // older version of CTDA? 20 bytes - case ESM4::SUB_SCHD: // 28 bytes - case ESM4::SUB_TCLT: // FormId choice - case ESM4::SUB_TCLF: // FormId - case ESM4::SUB_PNAM: // TES4 DLC - case ESM4::SUB_TPIC: // TES4 DLC - case ESM4::SUB_ANAM: // FO3 speaker formid - case ESM4::SUB_DNAM: // FO3 speech challenge - case ESM4::SUB_KNAM: // FO3 formid - case ESM4::SUB_LNAM: // FONV - case ESM4::SUB_TCFU: // FONV - case ESM4::SUB_TIFC: // TES5 - case ESM4::SUB_TWAT: // TES5 - case ESM4::SUB_CIS1: // TES5 - case ESM4::SUB_CIS2: // TES5 - case ESM4::SUB_CNAM: // TES5 - case ESM4::SUB_ENAM: // TES5 - case ESM4::SUB_EDID: // TES5 - case ESM4::SUB_VMAD: // TES5 - case ESM4::SUB_BNAM: // TES5 - case ESM4::SUB_SNAM: // TES5 - case ESM4::SUB_ONAM: // TES5 - case ESM4::SUB_QNAM: // TES5 for mScript - case ESM4::SUB_RNAM: // TES5 - case ESM4::SUB_ALFA: // FO4 - case ESM4::SUB_GNAM: // FO4 - case ESM4::SUB_GREE: // FO4 - case ESM4::SUB_INAM: // FO4 - case ESM4::SUB_INCC: // FO4 - case ESM4::SUB_INTV: // FO4 - case ESM4::SUB_IOVR: // FO4 - case ESM4::SUB_MODQ: // FO4 - case ESM4::SUB_NAM0: // FO4 - case ESM4::SUB_NAM4: // FO4 - case ESM4::SUB_NAM9: // FO4 - case ESM4::SUB_SRAF: // FO4 - case ESM4::SUB_TIQS: // FO4 - case ESM4::SUB_TNAM: // FO4 - case ESM4::SUB_TRDA: // FO4 - case ESM4::SUB_TSCE: // FO4 - case ESM4::SUB_WZMD: // FO4 + case ESM::fourCC("NAME"): // FormId add topic (not always present) + case ESM::fourCC("CTDT"): // older version of CTDA? 20 bytes + case ESM::fourCC("SCHD"): // 28 bytes + case ESM::fourCC("TCLT"): // FormId choice + case ESM::fourCC("TCLF"): // FormId + case ESM::fourCC("PNAM"): // TES4 DLC + case ESM::fourCC("TPIC"): // TES4 DLC + case ESM::fourCC("ANAM"): // FO3 speaker formid + case ESM::fourCC("DNAM"): // FO3 speech challenge + case ESM::fourCC("KNAM"): // FO3 formid + case ESM::fourCC("LNAM"): // FONV + case ESM::fourCC("TCFU"): // FONV + case ESM::fourCC("TIFC"): // TES5 + case ESM::fourCC("TWAT"): // TES5 + case ESM::fourCC("CIS1"): // TES5 + case ESM::fourCC("CIS2"): // TES5 + case ESM::fourCC("CNAM"): // TES5 + case ESM::fourCC("ENAM"): // TES5 + case ESM::fourCC("EDID"): // TES5 + case ESM::fourCC("VMAD"): // TES5 + case ESM::fourCC("BNAM"): // TES5 + case ESM::fourCC("SNAM"): // TES5 + case ESM::fourCC("ONAM"): // TES5 + case ESM::fourCC("QNAM"): // TES5 for mScript + case ESM::fourCC("RNAM"): // TES5 + case ESM::fourCC("ALFA"): // FO4 + case ESM::fourCC("GNAM"): // FO4 + case ESM::fourCC("GREE"): // FO4 + case ESM::fourCC("INAM"): // FO4 + case ESM::fourCC("INCC"): // FO4 + case ESM::fourCC("INTV"): // FO4 + case ESM::fourCC("IOVR"): // FO4 + case ESM::fourCC("MODQ"): // FO4 + case ESM::fourCC("NAM0"): // FO4 + case ESM::fourCC("NAM4"): // FO4 + case ESM::fourCC("NAM9"): // FO4 + case ESM::fourCC("SRAF"): // FO4 + case ESM::fourCC("TIQS"): // FO4 + case ESM::fourCC("TNAM"): // FO4 + case ESM::fourCC("TRDA"): // FO4 + case ESM::fourCC("TSCE"): // FO4 + case ESM::fourCC("WZMD"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadingr.cpp b/components/esm4/loadingr.cpp index d0b81fd4a1..64103058e5 100644 --- a/components/esm4/loadingr.cpp +++ b/components/esm4/loadingr.cpp @@ -42,10 +42,10 @@ void ESM4::Ingredient::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): { if (mFullName.empty()) { @@ -64,7 +64,7 @@ void ESM4::Ingredient::load(ESM4::Reader& reader) break; } } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { // if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) if (subHdr.dataSize == 8) // FO3 is size 4 even though VER_094 @@ -74,49 +74,49 @@ void ESM4::Ingredient::load(ESM4::Reader& reader) break; } - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ENIT: + case ESM::fourCC("ENIT"): reader.get(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_SCIT: + case ESM::fourCC("SCIT"): { reader.get(mEffect); reader.adjustFormId(mEffect.formId); break; } - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_EFID: - case ESM4::SUB_EFIT: - case ESM4::SUB_OBND: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_VMAD: - case ESM4::SUB_YNAM: - case ESM4::SUB_ZNAM: - case ESM4::SUB_ETYP: // FO3 - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("EFID"): + case ESM::fourCC("EFIT"): + case ESM::fourCC("OBND"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("YNAM"): + case ESM::fourCC("ZNAM"): + case ESM::fourCC("ETYP"): // FO3 + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end { reader.skipSubRecordData(); break; diff --git a/components/esm4/loadkeym.cpp b/components/esm4/loadkeym.cpp index 9b0c280b8b..b430f7ce3d 100644 --- a/components/esm4/loadkeym.cpp +++ b/components/esm4/loadkeym.cpp @@ -41,54 +41,54 @@ void ESM4::Key::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp index 2215b56dd1..53fb1de083 100644 --- a/components/esm4/loadland.cpp +++ b/components/esm4/loadland.cpp @@ -65,18 +65,18 @@ void ESM4::Land::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { reader.get(mLandFlags); break; } - case ESM4::SUB_VNML: // vertex normals, 33x33x(1+1+1) = 3267 + case ESM::fourCC("VNML"): // vertex normals, 33x33x(1+1+1) = 3267 { reader.get(mVertNorm); mDataTypes |= LAND_VNML; break; } - case ESM4::SUB_VHGT: // vertex height gradient, 4+33x33+3 = 4+1089+3 = 1096 + case ESM::fourCC("VHGT"): // vertex height gradient, 4+33x33+3 = 4+1089+3 = 1096 { #if 0 reader.get(mHeightMap.heightOffset); @@ -88,13 +88,13 @@ void ESM4::Land::load(ESM4::Reader& reader) mDataTypes |= LAND_VHGT; break; } - case ESM4::SUB_VCLR: // vertex colours, 24bit RGB, 33x33x(1+1+1) = 3267 + case ESM::fourCC("VCLR"): // vertex colours, 24bit RGB, 33x33x(1+1+1) = 3267 { reader.get(mVertColr); mDataTypes |= LAND_VCLR; break; } - case ESM4::SUB_BTXT: + case ESM::fourCC("BTXT"): { BTXT base; if (reader.getExact(base)) @@ -112,7 +112,7 @@ void ESM4::Land::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_ATXT: + case ESM::fourCC("ATXT"): { if (currentAddQuad != -1) { @@ -144,7 +144,7 @@ void ESM4::Land::load(ESM4::Reader& reader) currentAddQuad = layer.texture.quadrant; break; } - case ESM4::SUB_VTXT: + case ESM::fourCC("VTXT"): { if (currentAddQuad == -1) throw std::runtime_error("VTXT without ATXT found"); @@ -177,7 +177,7 @@ void ESM4::Land::load(ESM4::Reader& reader) // std::cout << "VTXT: count " << std::dec << count << std::endl; break; } - case ESM4::SUB_VTEX: // only in Oblivion? + case ESM::fourCC("VTEX"): // only in Oblivion? { const std::uint16_t count = reader.subRecordHeader().dataSize / sizeof(ESM::FormId32); if ((reader.subRecordHeader().dataSize % sizeof(ESM::FormId32)) != 0) @@ -191,7 +191,7 @@ void ESM4::Land::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MPCD: // FO4 + case ESM::fourCC("MPCD"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadlgtm.cpp b/components/esm4/loadlgtm.cpp index 0959be10a2..ce895ea5b8 100644 --- a/components/esm4/loadlgtm.cpp +++ b/components/esm4/loadlgtm.cpp @@ -44,10 +44,10 @@ void ESM4::LightingTemplate::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): if (subHdr.dataSize == 36) // TES4 reader.get(&mLighting, 36); if (subHdr.dataSize == 40) // FO3/FONV @@ -60,7 +60,7 @@ void ESM4::LightingTemplate::load(ESM4::Reader& reader) else reader.skipSubRecordData(); // throw? break; - case ESM4::SUB_DALC: // TES5 + case ESM::fourCC("DALC"): // TES5 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadligh.cpp b/components/esm4/loadligh.cpp index 0848ee8435..a0d467bafc 100644 --- a/components/esm4/loadligh.cpp +++ b/components/esm4/loadligh.cpp @@ -40,13 +40,13 @@ void ESM4::Light::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize != 32 && subHdr.dataSize != 48 && subHdr.dataSize != 64) { @@ -78,47 +78,47 @@ void ESM4::Light::load(ESM4::Reader& reader) reader.get(mData.weight); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mSound); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mFade); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_LNAM: // FO4 - case ESM4::SUB_MICO: // FO4 - case ESM4::SUB_NAM0: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_WGDR: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("LNAM"): // FO4 + case ESM::fourCC("MICO"): // FO4 + case ESM::fourCC("NAM0"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("WGDR"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadltex.cpp b/components/esm4/loadltex.cpp index 9b2d12034f..a8b6c9ec81 100644 --- a/components/esm4/loadltex.cpp +++ b/components/esm4/loadltex.cpp @@ -41,10 +41,10 @@ void ESM4::LandTexture::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): { switch (subHdr.dataSize) { @@ -61,22 +61,22 @@ void ESM4::LandTexture::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mTextureFile); break; // Oblivion only? - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.get(mTextureSpecular); break; - case ESM4::SUB_GNAM: + case ESM::fourCC("GNAM"): reader.getFormId(mGrass.emplace_back()); break; - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.getFormId(mTexture); break; // TES5, FO4 - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): reader.getFormId(mMaterial); break; // TES5, FO4 - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.get(mMaterialFlags); break; // SSE default: diff --git a/components/esm4/loadlvlc.cpp b/components/esm4/loadlvlc.cpp index b1a0a0f241..8ce2497bcc 100644 --- a/components/esm4/loadlvlc.cpp +++ b/components/esm4/loadlvlc.cpp @@ -41,22 +41,22 @@ void ESM4::LevelledCreature::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_TNAM: + case ESM::fourCC("TNAM"): reader.getFormId(mTemplate); break; - case ESM4::SUB_LVLD: + case ESM::fourCC("LVLD"): reader.get(mChanceNone); break; - case ESM4::SUB_LVLF: + case ESM::fourCC("LVLF"): reader.get(mLvlCreaFlags); break; - case ESM4::SUB_LVLO: + case ESM::fourCC("LVLO"): { static LVLO lvlo; if (subHdr.dataSize != 12) @@ -83,7 +83,7 @@ void ESM4::LevelledCreature::load(ESM4::Reader& reader) mLvlObject.push_back(lvlo); break; } - case ESM4::SUB_OBND: // FO3 + case ESM::fourCC("OBND"): // FO3 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadlvli.cpp b/components/esm4/loadlvli.cpp index cab8db4a21..51e3e33a55 100644 --- a/components/esm4/loadlvli.cpp +++ b/components/esm4/loadlvli.cpp @@ -41,20 +41,20 @@ void ESM4::LevelledItem::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_LVLD: + case ESM::fourCC("LVLD"): reader.get(mChanceNone); break; - case ESM4::SUB_LVLF: + case ESM::fourCC("LVLF"): reader.get(mLvlItemFlags); mHasLvlItemFlags = true; break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_LVLO: + case ESM::fourCC("LVLO"): { static LVLO lvlo; if (subHdr.dataSize != 12) @@ -76,14 +76,14 @@ void ESM4::LevelledItem::load(ESM4::Reader& reader) mLvlObject.push_back(lvlo); break; } - case ESM4::SUB_LLCT: - case ESM4::SUB_OBND: // FO3/FONV - case ESM4::SUB_COED: // FO3/FONV - case ESM4::SUB_LVLG: // FO3/FONV - case ESM4::SUB_LLKC: // FO4 - case ESM4::SUB_LVLM: // FO4 - case ESM4::SUB_LVSG: // FO4 - case ESM4::SUB_ONAM: // FO4 + case ESM::fourCC("LLCT"): + case ESM::fourCC("OBND"): // FO3/FONV + case ESM::fourCC("COED"): // FO3/FONV + case ESM::fourCC("LVLG"): // FO3/FONV + case ESM::fourCC("LLKC"): // FO4 + case ESM::fourCC("LVLM"): // FO4 + case ESM::fourCC("LVSG"): // FO4 + case ESM::fourCC("ONAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadlvln.cpp b/components/esm4/loadlvln.cpp index febdcbeca9..6633d6ad7b 100644 --- a/components/esm4/loadlvln.cpp +++ b/components/esm4/loadlvln.cpp @@ -42,22 +42,22 @@ void ESM4::LevelledNpc::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_LLCT: + case ESM::fourCC("LLCT"): reader.get(mListCount); break; - case ESM4::SUB_LVLD: + case ESM::fourCC("LVLD"): reader.get(mChanceNone); break; - case ESM4::SUB_LVLF: + case ESM::fourCC("LVLF"): reader.get(mLvlActorFlags); break; - case ESM4::SUB_LVLO: + case ESM::fourCC("LVLO"): { static LVLO lvlo; if (subHdr.dataSize != 12) @@ -89,15 +89,15 @@ void ESM4::LevelledNpc::load(ESM4::Reader& reader) mLvlObject.push_back(lvlo); break; } - case ESM4::SUB_COED: // owner - case ESM4::SUB_OBND: // object bounds - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_LLKC: // FO4 - case ESM4::SUB_LVLG: // FO4 - case ESM4::SUB_LVLM: // FO4 + case ESM::fourCC("COED"): // owner + case ESM::fourCC("OBND"): // object bounds + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("LLKC"): // FO4 + case ESM::fourCC("LVLG"): // FO4 + case ESM::fourCC("LVLM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmato.cpp b/components/esm4/loadmato.cpp index 13d5e7d83d..6d45b689ba 100644 --- a/components/esm4/loadmato.cpp +++ b/components/esm4/loadmato.cpp @@ -41,18 +41,18 @@ void ESM4::Material::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_DNAM: - case ESM4::SUB_DATA: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end + case ESM::fourCC("DNAM"): + case ESM::fourCC("DATA"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmisc.cpp b/components/esm4/loadmisc.cpp index 6dfd69148d..b27e38f055 100644 --- a/components/esm4/loadmisc.cpp +++ b/components/esm4/loadmisc.cpp @@ -41,58 +41,58 @@ void ESM4::MiscItem::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_RNAM: // FONV - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_CDIX: // FO4 - case ESM4::SUB_CVPA: // FO4 - case ESM4::SUB_FIMD: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("RNAM"): // FONV + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("CDIX"): // FO4 + case ESM::fourCC("CVPA"): // FO4 + case ESM::fourCC("FIMD"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmset.cpp b/components/esm4/loadmset.cpp index e15c508bc1..f7c088c47f 100644 --- a/components/esm4/loadmset.cpp +++ b/components/esm4/loadmset.cpp @@ -41,91 +41,91 @@ void ESM4::MediaSet::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mFullName); break; - case ESM4::SUB_NAM1: + case ESM::fourCC("NAM1"): reader.get(mSetType); break; - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mEnabled); break; - case ESM4::SUB_NAM2: + case ESM::fourCC("NAM2"): reader.getZString(mSet2); break; - case ESM4::SUB_NAM3: + case ESM::fourCC("NAM3"): reader.getZString(mSet3); break; - case ESM4::SUB_NAM4: + case ESM::fourCC("NAM4"): reader.getZString(mSet4); break; - case ESM4::SUB_NAM5: + case ESM::fourCC("NAM5"): reader.getZString(mSet5); break; - case ESM4::SUB_NAM6: + case ESM::fourCC("NAM6"): reader.getZString(mSet6); break; - case ESM4::SUB_NAM7: + case ESM::fourCC("NAM7"): reader.getZString(mSet7); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): reader.getFormId(mSoundIntro); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mSoundOutro); break; - case ESM4::SUB_NAM8: + case ESM::fourCC("NAM8"): reader.get(mLevel8); break; - case ESM4::SUB_NAM9: + case ESM::fourCC("NAM9"): reader.get(mLevel9); break; - case ESM4::SUB_NAM0: + case ESM::fourCC("NAM0"): reader.get(mLevel0); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mLevelA); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): reader.get(mLevelB); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.get(mLevelC); break; - case ESM4::SUB_JNAM: + case ESM::fourCC("JNAM"): reader.get(mBoundaryDayOuter); break; - case ESM4::SUB_KNAM: + case ESM::fourCC("KNAM"): reader.get(mBoundaryDayMiddle); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.get(mBoundaryDayInner); break; - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): reader.get(mBoundaryNightOuter); break; - case ESM4::SUB_NNAM: + case ESM::fourCC("NNAM"): reader.get(mBoundaryNightMiddle); break; - case ESM4::SUB_ONAM: + case ESM::fourCC("ONAM"): reader.get(mBoundaryNightInner); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.get(mTime1); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.get(mTime2); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.get(mTime3); break; - case ESM4::SUB_GNAM: + case ESM::fourCC("GNAM"): reader.get(mTime4); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmstt.cpp b/components/esm4/loadmstt.cpp index 14091e96f0..3e0cc9ea2d 100644 --- a/components/esm4/loadmstt.cpp +++ b/components/esm4/loadmstt.cpp @@ -41,41 +41,41 @@ void ESM4::MovableStatic::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mLoopingSound); break; - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_OBND: // object bounds - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_VMAD: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_MODB: - case ESM4::SUB_PRPS: - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("OBND"): // object bounds + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("MODB"): + case ESM::fourCC("PRPS"): + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadmusc.cpp b/components/esm4/loadmusc.cpp index 47ed71b2cf..a06b4dc81c 100644 --- a/components/esm4/loadmusc.cpp +++ b/components/esm4/loadmusc.cpp @@ -43,16 +43,16 @@ void ESM4::Music::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.getZString(mMusicFile); break; - case ESM4::SUB_ANAM: // FONV float (attenuation in db? loop if positive?) - case ESM4::SUB_WNAM: // TES5 - case ESM4::SUB_PNAM: // TES5 - case ESM4::SUB_TNAM: // TES5 + case ESM::fourCC("ANAM"): // FONV float (attenuation in db? loop if positive?) + case ESM::fourCC("WNAM"): // TES5 + case ESM::fourCC("PNAM"): // TES5 + case ESM::fourCC("TNAM"): // TES5 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadnavi.cpp b/components/esm4/loadnavi.cpp index 5b73af606e..47befbf268 100644 --- a/components/esm4/loadnavi.cpp +++ b/components/esm4/loadnavi.cpp @@ -241,13 +241,13 @@ void ESM4::Navigation::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: // seems to be unused? + case ESM::fourCC("EDID"): // seems to be unused? { if (!reader.getZString(mEditorId)) throw std::runtime_error("NAVI EDID data read error"); break; } - case ESM4::SUB_NVPP: + case ESM::fourCC("NVPP"): { // FIXME: FO4 updates the format if (reader.hasFormVersion() && (esmVer == ESM::VER_095 || esmVer == ESM::VER_100)) @@ -330,14 +330,14 @@ void ESM4::Navigation::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_NVER: + case ESM::fourCC("NVER"): { std::uint32_t version; // always the same? (0x0c) reader.get(version); // TODO: store this or use it for merging? // std::cout << "NAVI version " << std::dec << version << std::endl; break; } - case ESM4::SUB_NVMI: // multiple + case ESM::fourCC("NVMI"): // multiple { // Can only read TES4 navmesh data // Note FO4 FIXME above @@ -353,8 +353,8 @@ void ESM4::Navigation::load(ESM4::Reader& reader) mNavMeshInfo.push_back(nvmi); break; } - case ESM4::SUB_NVSI: // from Dawnguard onwards - case ESM4::SUB_NVCI: // FO3 + case ESM::fourCC("NVSI"): // from Dawnguard onwards + case ESM::fourCC("NVCI"): // FO3 { reader.skipSubRecordData(); // FIXME: break; diff --git a/components/esm4/loadnavm.cpp b/components/esm4/loadnavm.cpp index 828fd77ca1..ebe6a7dbbb 100644 --- a/components/esm4/loadnavm.cpp +++ b/components/esm4/loadnavm.cpp @@ -209,7 +209,7 @@ void ESM4::NavMesh::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_NVNM: + case ESM::fourCC("NVNM"): { // See FIXME in ESM4::Navigation::load. // FO4 updates the format @@ -224,19 +224,19 @@ void ESM4::NavMesh::load(ESM4::Reader& reader) mData.push_back(nvnm); // FIXME try swap break; } - case ESM4::SUB_ONAM: - case ESM4::SUB_PNAM: - case ESM4::SUB_NNAM: - case ESM4::SUB_NVER: // FO3 - case ESM4::SUB_DATA: // FO3 - case ESM4::SUB_NVVX: // FO3 - case ESM4::SUB_NVTR: // FO3 - case ESM4::SUB_NVCA: // FO3 - case ESM4::SUB_NVDP: // FO3 - case ESM4::SUB_NVGD: // FO3 - case ESM4::SUB_NVEX: // FO3 - case ESM4::SUB_EDID: // FO3 - case ESM4::SUB_MNAM: // FO4 + case ESM::fourCC("ONAM"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("NNAM"): + case ESM::fourCC("NVER"): // FO3 + case ESM::fourCC("DATA"): // FO3 + case ESM::fourCC("NVVX"): // FO3 + case ESM::fourCC("NVTR"): // FO3 + case ESM::fourCC("NVCA"): // FO3 + case ESM::fourCC("NVDP"): // FO3 + case ESM::fourCC("NVGD"): // FO3 + case ESM::fourCC("NVEX"): // FO3 + case ESM::fourCC("EDID"): // FO3 + case ESM::fourCC("MNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadnote.cpp b/components/esm4/loadnote.cpp index 9c1b4b3140..aee7909e88 100644 --- a/components/esm4/loadnote.cpp +++ b/components/esm4/loadnote.cpp @@ -41,41 +41,41 @@ void ESM4::Note::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_DATA: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_ONAM: - case ESM4::SUB_SNAM: - case ESM4::SUB_TNAM: - case ESM4::SUB_XNAM: - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_PNAM: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("DATA"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("ONAM"): + case ESM::fourCC("SNAM"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("XNAM"): + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("PNAM"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp index 885263d67b..9b8a1679ef 100644 --- a/components/esm4/loadnpc.cpp +++ b/components/esm4/loadnpc.cpp @@ -48,16 +48,16 @@ void ESM4::Npc::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; // not for TES5, see Race - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_CNTO: + case ESM::fourCC("CNTO"): { static InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); @@ -65,13 +65,13 @@ void ESM4::Npc::load(ESM4::Reader& reader) mInventory.push_back(inv); break; } - case ESM4::SUB_SPLO: + case ESM::fourCC("SPLO"): reader.getFormId(mSpell.emplace_back()); break; - case ESM4::SUB_PKID: + case ESM::fourCC("PKID"): reader.getFormId(mAIPackages.emplace_back()); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): { // FO4, FO76 if (subHdr.dataSize == 5) @@ -81,27 +81,27 @@ void ESM4::Npc::load(ESM4::Reader& reader) reader.adjustFormId(mFaction.faction); break; } - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getFormId(mRace); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getFormId(mClass); break; - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): reader.getFormId(mHair); break; // not for TES5 - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEyes); break; // - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mDeathItem); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; // - case ESM4::SUB_AIDT: + case ESM::fourCC("AIDT"): { if (subHdr.dataSize != 12) { @@ -112,7 +112,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) reader.get(mAIData); // TES4 break; } - case ESM4::SUB_ACBS: + case ESM::fourCC("ACBS"): { switch (subHdr.dataSize) { @@ -129,7 +129,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize == 0) break; @@ -140,19 +140,19 @@ void ESM4::Npc::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mCombatStyle); break; - case ESM4::SUB_CSCR: + case ESM::fourCC("CSCR"): reader.getFormId(mSoundBase); break; - case ESM4::SUB_CSDI: + case ESM::fourCC("CSDI"): reader.getFormId(mSound); break; - case ESM4::SUB_CSDC: + case ESM::fourCC("CSDC"): reader.get(mSoundChance); break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): { // FIXME: should be read into mWornArmor for FO4 if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) @@ -161,10 +161,10 @@ void ESM4::Npc::load(ESM4::Reader& reader) reader.get(mFootWeight); break; } - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_KFFZ: + case ESM::fourCC("KFFZ"): { // Seems to be only below 3, and only happens 3 times while loading TES4: // Forward_SheogorathWithCane.kf @@ -174,10 +174,10 @@ void ESM4::Npc::load(ESM4::Reader& reader) throw std::runtime_error("NPC_ KFFZ data read error"); break; } - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.get(mHairLength); break; - case ESM4::SUB_HCLR: + case ESM::fourCC("HCLR"): { reader.get(mHairColour.red); reader.get(mHairColour.green); @@ -186,10 +186,10 @@ void ESM4::Npc::load(ESM4::Reader& reader) break; } - case ESM4::SUB_TPLT: + case ESM::fourCC("TPLT"): reader.getFormId(mBaseTemplate); break; - case ESM4::SUB_FGGS: + case ESM::fourCC("FGGS"): { mSymShapeModeCoefficients.resize(50); for (std::size_t i = 0; i < 50; ++i) @@ -197,7 +197,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGGA: + case ESM::fourCC("FGGA"): { mAsymShapeModeCoefficients.resize(30); for (std::size_t i = 0; i < 30; ++i) @@ -205,7 +205,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGTS: + case ESM::fourCC("FGTS"): { mSymTextureModeCoefficients.resize(50); for (std::size_t i = 0; i < 50; ++i) @@ -213,122 +213,122 @@ void ESM4::Npc::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): { reader.get(mFgRace); // std::cout << "race " << mEditorId << " " << mRace << std::endl; // FIXME // std::cout << "fg race " << mEditorId << " " << mFgRace << std::endl; // FIXME break; } - case ESM4::SUB_PNAM: // FO3/FONV/TES5 + case ESM::fourCC("PNAM"): // FO3/FONV/TES5 reader.getFormId(mHeadParts.emplace_back()); break; - case ESM4::SUB_HCLF: // TES5 hair colour + case ESM::fourCC("HCLF"): // TES5 hair colour { reader.getFormId(mHairColourId); break; } - case ESM4::SUB_BCLF: + case ESM::fourCC("BCLF"): { reader.getFormId(mBeardColourId); break; } - case ESM4::SUB_COCT: // TES5 + case ESM::fourCC("COCT"): // TES5 { std::uint32_t count; reader.get(count); break; } - case ESM4::SUB_DOFT: + case ESM::fourCC("DOFT"): reader.getFormId(mDefaultOutfit); break; - case ESM4::SUB_SOFT: + case ESM::fourCC("SOFT"): reader.getFormId(mSleepOutfit); break; - case ESM4::SUB_DPLT: + case ESM::fourCC("DPLT"): reader.getFormId(mDefaultPkg); break; // AI package list - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_NAM6: // height mult - case ESM4::SUB_NAM7: // weight mult - case ESM4::SUB_ATKR: - case ESM4::SUB_CRIF: - case ESM4::SUB_CSDT: - case ESM4::SUB_DNAM: - case ESM4::SUB_ECOR: - case ESM4::SUB_ANAM: - case ESM4::SUB_ATKD: - case ESM4::SUB_ATKE: - case ESM4::SUB_FTST: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_NAM5: - case ESM4::SUB_NAM8: - case ESM4::SUB_NAM9: - case ESM4::SUB_NAMA: - case ESM4::SUB_OBND: - case ESM4::SUB_PRKR: - case ESM4::SUB_PRKZ: - case ESM4::SUB_QNAM: - case ESM4::SUB_SPCT: - case ESM4::SUB_TIAS: - case ESM4::SUB_TINC: - case ESM4::SUB_TINI: - case ESM4::SUB_TINV: - case ESM4::SUB_VMAD: - case ESM4::SUB_VTCK: - case ESM4::SUB_GNAM: - case ESM4::SUB_SHRT: - case ESM4::SUB_SPOR: - case ESM4::SUB_EAMT: // FO3 - case ESM4::SUB_NAM4: // FO3 - case ESM4::SUB_COED: // FO3 - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_ATKS: // FO4 - case ESM4::SUB_ATKT: // FO4 - case ESM4::SUB_ATKW: // FO4 - case ESM4::SUB_ATTX: // FO4 - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_LTPT: // FO4 - case ESM4::SUB_LTPC: // FO4 - case ESM4::SUB_MWGT: // FO4 - case ESM4::SUB_NTRM: // FO4 - case ESM4::SUB_PFRN: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_STCP: // FO4 - case ESM4::SUB_TETI: // FO4 - case ESM4::SUB_TEND: // FO4 - case ESM4::SUB_TPTA: // FO4 - case ESM4::SUB_OBTE: // FO4 object template start - case ESM4::SUB_OBTF: // - case ESM4::SUB_OBTS: // - case ESM4::SUB_STOP: // FO4 object template end - case ESM4::SUB_OCOR: // FO4 new package lists start - case ESM4::SUB_GWOR: // - case ESM4::SUB_FCPL: // - case ESM4::SUB_RCLR: // FO4 new package lists end - case ESM4::SUB_CS2D: // FO4 actor sound subrecords - case ESM4::SUB_CS2E: // - case ESM4::SUB_CS2F: // - case ESM4::SUB_CS2H: // - case ESM4::SUB_CS2K: // FO4 actor sound subrecords end - case ESM4::SUB_MSDK: // FO4 morph subrecords start - case ESM4::SUB_MSDV: // - case ESM4::SUB_MRSV: // - case ESM4::SUB_FMRI: // - case ESM4::SUB_FMRS: // - case ESM4::SUB_FMIN: // FO4 morph subrecords end + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("NAM6"): // height mult + case ESM::fourCC("NAM7"): // weight mult + case ESM::fourCC("ATKR"): + case ESM::fourCC("CRIF"): + case ESM::fourCC("CSDT"): + case ESM::fourCC("DNAM"): + case ESM::fourCC("ECOR"): + case ESM::fourCC("ANAM"): + case ESM::fourCC("ATKD"): + case ESM::fourCC("ATKE"): + case ESM::fourCC("FTST"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("NAM5"): + case ESM::fourCC("NAM8"): + case ESM::fourCC("NAM9"): + case ESM::fourCC("NAMA"): + case ESM::fourCC("OBND"): + case ESM::fourCC("PRKR"): + case ESM::fourCC("PRKZ"): + case ESM::fourCC("QNAM"): + case ESM::fourCC("SPCT"): + case ESM::fourCC("TIAS"): + case ESM::fourCC("TINC"): + case ESM::fourCC("TINI"): + case ESM::fourCC("TINV"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("VTCK"): + case ESM::fourCC("GNAM"): + case ESM::fourCC("SHRT"): + case ESM::fourCC("SPOR"): + case ESM::fourCC("EAMT"): // FO3 + case ESM::fourCC("NAM4"): // FO3 + case ESM::fourCC("COED"): // FO3 + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("ATKS"): // FO4 + case ESM::fourCC("ATKT"): // FO4 + case ESM::fourCC("ATKW"): // FO4 + case ESM::fourCC("ATTX"): // FO4 + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("LTPT"): // FO4 + case ESM::fourCC("LTPC"): // FO4 + case ESM::fourCC("MWGT"): // FO4 + case ESM::fourCC("NTRM"): // FO4 + case ESM::fourCC("PFRN"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("STCP"): // FO4 + case ESM::fourCC("TETI"): // FO4 + case ESM::fourCC("TEND"): // FO4 + case ESM::fourCC("TPTA"): // FO4 + case ESM::fourCC("OBTE"): // FO4 object template start + case ESM::fourCC("OBTF"): // + case ESM::fourCC("OBTS"): // + case ESM::fourCC("STOP"): // FO4 object template end + case ESM::fourCC("OCOR"): // FO4 new package lists start + case ESM::fourCC("GWOR"): // + case ESM::fourCC("FCPL"): // + case ESM::fourCC("RCLR"): // FO4 new package lists end + case ESM::fourCC("CS2D"): // FO4 actor sound subrecords + case ESM::fourCC("CS2E"): // + case ESM::fourCC("CS2F"): // + case ESM::fourCC("CS2H"): // + case ESM::fourCC("CS2K"): // FO4 actor sound subrecords end + case ESM::fourCC("MSDK"): // FO4 morph subrecords start + case ESM::fourCC("MSDV"): // + case ESM::fourCC("MRSV"): // + case ESM::fourCC("FMRI"): // + case ESM::fourCC("FMRS"): // + case ESM::fourCC("FMIN"): // FO4 morph subrecords end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadotft.cpp b/components/esm4/loadotft.cpp index b980de4a8c..a5fec9b002 100644 --- a/components/esm4/loadotft.cpp +++ b/components/esm4/loadotft.cpp @@ -41,10 +41,10 @@ void ESM4::Outfit::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): { mInventory.resize(subHdr.dataSize / sizeof(ESM::FormId32)); for (ESM::FormId& formId : mInventory) diff --git a/components/esm4/loadpack.cpp b/components/esm4/loadpack.cpp index ab75598121..5d81e38f43 100644 --- a/components/esm4/loadpack.cpp +++ b/components/esm4/loadpack.cpp @@ -42,10 +42,10 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_PKDT: + case ESM::fourCC("PKDT"): { if (subHdr.dataSize != sizeof(PKDT) && subHdr.dataSize == 4) { @@ -60,7 +60,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PSDT: // reader.get(mSchedule); break; + case ESM::fourCC("PSDT"): // reader.get(mSchedule); break; { if (subHdr.dataSize != sizeof(mSchedule)) reader.skipSubRecordData(); // FIXME: @@ -69,7 +69,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PLDT: + case ESM::fourCC("PLDT"): { if (subHdr.dataSize != sizeof(mLocation)) reader.skipSubRecordData(); // FIXME: @@ -82,7 +82,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PTDT: + case ESM::fourCC("PTDT"): { if (subHdr.dataSize != sizeof(mTarget)) reader.skipSubRecordData(); // FIXME: FO3 @@ -95,7 +95,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_CTDA: + case ESM::fourCC("CTDA"): { if (subHdr.dataSize != sizeof(CTDA)) { @@ -112,55 +112,55 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - case ESM4::SUB_CTDT: // always 20 for TES4 - case ESM4::SUB_TNAM: // FO3 - case ESM4::SUB_INAM: // FO3 - case ESM4::SUB_CNAM: // FO3 - case ESM4::SUB_SCHR: // FO3 - case ESM4::SUB_POBA: // FO3 - case ESM4::SUB_POCA: // FO3 - case ESM4::SUB_POEA: // FO3 - case ESM4::SUB_SCTX: // FO3 - case ESM4::SUB_SCDA: // FO3 - case ESM4::SUB_SCRO: // FO3 - case ESM4::SUB_IDLA: // FO3 - case ESM4::SUB_IDLC: // FO3 - case ESM4::SUB_IDLF: // FO3 - case ESM4::SUB_IDLT: // FO3 - case ESM4::SUB_PKDD: // FO3 - case ESM4::SUB_PKD2: // FO3 - case ESM4::SUB_PKPT: // FO3 - case ESM4::SUB_PKED: // FO3 - case ESM4::SUB_PKE2: // FO3 - case ESM4::SUB_PKAM: // FO3 - case ESM4::SUB_PUID: // FO3 - case ESM4::SUB_PKW3: // FO3 - case ESM4::SUB_PTD2: // FO3 - case ESM4::SUB_PLD2: // FO3 - case ESM4::SUB_PKFD: // FO3 - case ESM4::SUB_SLSD: // FO3 - case ESM4::SUB_SCVR: // FO3 - case ESM4::SUB_SCRV: // FO3 - case ESM4::SUB_IDLB: // FO3 - case ESM4::SUB_ANAM: // TES5 - case ESM4::SUB_BNAM: // TES5 - case ESM4::SUB_FNAM: // TES5 - case ESM4::SUB_PNAM: // TES5 - case ESM4::SUB_QNAM: // TES5 - case ESM4::SUB_UNAM: // TES5 - case ESM4::SUB_XNAM: // TES5 - case ESM4::SUB_PDTO: // TES5 - case ESM4::SUB_PTDA: // TES5 - case ESM4::SUB_PFOR: // TES5 - case ESM4::SUB_PFO2: // TES5 - case ESM4::SUB_PRCB: // TES5 - case ESM4::SUB_PKCU: // TES5 - case ESM4::SUB_PKC2: // TES5 - case ESM4::SUB_CITC: // TES5 - case ESM4::SUB_CIS1: // TES5 - case ESM4::SUB_CIS2: // TES5 - case ESM4::SUB_VMAD: // TES5 - case ESM4::SUB_TPIC: // TES5 + case ESM::fourCC("CTDT"): // always 20 for TES4 + case ESM::fourCC("TNAM"): // FO3 + case ESM::fourCC("INAM"): // FO3 + case ESM::fourCC("CNAM"): // FO3 + case ESM::fourCC("SCHR"): // FO3 + case ESM::fourCC("POBA"): // FO3 + case ESM::fourCC("POCA"): // FO3 + case ESM::fourCC("POEA"): // FO3 + case ESM::fourCC("SCTX"): // FO3 + case ESM::fourCC("SCDA"): // FO3 + case ESM::fourCC("SCRO"): // FO3 + case ESM::fourCC("IDLA"): // FO3 + case ESM::fourCC("IDLC"): // FO3 + case ESM::fourCC("IDLF"): // FO3 + case ESM::fourCC("IDLT"): // FO3 + case ESM::fourCC("PKDD"): // FO3 + case ESM::fourCC("PKD2"): // FO3 + case ESM::fourCC("PKPT"): // FO3 + case ESM::fourCC("PKED"): // FO3 + case ESM::fourCC("PKE2"): // FO3 + case ESM::fourCC("PKAM"): // FO3 + case ESM::fourCC("PUID"): // FO3 + case ESM::fourCC("PKW3"): // FO3 + case ESM::fourCC("PTD2"): // FO3 + case ESM::fourCC("PLD2"): // FO3 + case ESM::fourCC("PKFD"): // FO3 + case ESM::fourCC("SLSD"): // FO3 + case ESM::fourCC("SCVR"): // FO3 + case ESM::fourCC("SCRV"): // FO3 + case ESM::fourCC("IDLB"): // FO3 + case ESM::fourCC("ANAM"): // TES5 + case ESM::fourCC("BNAM"): // TES5 + case ESM::fourCC("FNAM"): // TES5 + case ESM::fourCC("PNAM"): // TES5 + case ESM::fourCC("QNAM"): // TES5 + case ESM::fourCC("UNAM"): // TES5 + case ESM::fourCC("XNAM"): // TES5 + case ESM::fourCC("PDTO"): // TES5 + case ESM::fourCC("PTDA"): // TES5 + case ESM::fourCC("PFOR"): // TES5 + case ESM::fourCC("PFO2"): // TES5 + case ESM::fourCC("PRCB"): // TES5 + case ESM::fourCC("PKCU"): // TES5 + case ESM::fourCC("PKC2"): // TES5 + case ESM::fourCC("CITC"): // TES5 + case ESM::fourCC("CIS1"): // TES5 + case ESM::fourCC("CIS2"): // TES5 + case ESM::fourCC("VMAD"): // TES5 + case ESM::fourCC("TPIC"): // TES5 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadpgrd.cpp b/components/esm4/loadpgrd.cpp index 12cbf6f28b..4246e7517e 100644 --- a/components/esm4/loadpgrd.cpp +++ b/components/esm4/loadpgrd.cpp @@ -44,10 +44,10 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_PGRP: + case ESM::fourCC("PGRP"): { std::size_t numNodes = subHdr.dataSize / sizeof(PGRP); if (numNodes != std::size_t(mData)) // keep gcc quiet @@ -66,7 +66,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGRR: + case ESM::fourCC("PGRR"): { static PGRR link; @@ -91,7 +91,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGRI: + case ESM::fourCC("PGRI"): { std::size_t numForeign = subHdr.dataSize / sizeof(PGRI); mForeign.resize(numForeign); @@ -103,7 +103,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGRL: + case ESM::fourCC("PGRL"): { static PGRL objLink; reader.getFormId(objLink.object); @@ -118,7 +118,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGAG: + case ESM::fourCC("PGAG"): { #if 0 std::vector mDataBuf(subHdr.dataSize); diff --git a/components/esm4/loadpgre.cpp b/components/esm4/loadpgre.cpp index 4e473bd47a..123d2c967a 100644 --- a/components/esm4/loadpgre.cpp +++ b/components/esm4/loadpgre.cpp @@ -43,51 +43,51 @@ void ESM4::PlacedGrenade::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_NAME: - case ESM4::SUB_XEZN: - case ESM4::SUB_XRGD: - case ESM4::SUB_XRGB: - case ESM4::SUB_XPRD: - case ESM4::SUB_XPPA: - case ESM4::SUB_INAM: - case ESM4::SUB_TNAM: - case ESM4::SUB_XOWN: - case ESM4::SUB_XRNK: - case ESM4::SUB_XCNT: - case ESM4::SUB_XRDS: - case ESM4::SUB_XHLP: - case ESM4::SUB_XPWR: - case ESM4::SUB_XDCR: - case ESM4::SUB_XLKR: - case ESM4::SUB_XLKT: // FO4 - case ESM4::SUB_XCLP: - case ESM4::SUB_XAPD: - case ESM4::SUB_XAPR: - case ESM4::SUB_XATO: - case ESM4::SUB_XESP: - case ESM4::SUB_XEMI: - case ESM4::SUB_XMBR: - case ESM4::SUB_XIBS: - case ESM4::SUB_XSCL: - case ESM4::SUB_DATA: - case ESM4::SUB_VMAD: - case ESM4::SUB_MNAM: // FO4 - case ESM4::SUB_XAMC: // FO4 - case ESM4::SUB_XASP: // FO4 - case ESM4::SUB_XATP: // FO4 - case ESM4::SUB_XCVR: // FO4 - case ESM4::SUB_XFVC: // FO4 - case ESM4::SUB_XHTW: // FO4 - case ESM4::SUB_XIS2: // FO4 - case ESM4::SUB_XLOD: // FO4 - case ESM4::SUB_XLRL: // FO4 - case ESM4::SUB_XLRT: // FO4 - case ESM4::SUB_XLYR: // FO4 - case ESM4::SUB_XMSP: // FO4 - case ESM4::SUB_XRFG: // FO4 + case ESM::fourCC("NAME"): + case ESM::fourCC("XEZN"): + case ESM::fourCC("XRGD"): + case ESM::fourCC("XRGB"): + case ESM::fourCC("XPRD"): + case ESM::fourCC("XPPA"): + case ESM::fourCC("INAM"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("XOWN"): + case ESM::fourCC("XRNK"): + case ESM::fourCC("XCNT"): + case ESM::fourCC("XRDS"): + case ESM::fourCC("XHLP"): + case ESM::fourCC("XPWR"): + case ESM::fourCC("XDCR"): + case ESM::fourCC("XLKR"): + case ESM::fourCC("XLKT"): // FO4 + case ESM::fourCC("XCLP"): + case ESM::fourCC("XAPD"): + case ESM::fourCC("XAPR"): + case ESM::fourCC("XATO"): + case ESM::fourCC("XESP"): + case ESM::fourCC("XEMI"): + case ESM::fourCC("XMBR"): + case ESM::fourCC("XIBS"): + case ESM::fourCC("XSCL"): + case ESM::fourCC("DATA"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("MNAM"): // FO4 + case ESM::fourCC("XAMC"): // FO4 + case ESM::fourCC("XASP"): // FO4 + case ESM::fourCC("XATP"): // FO4 + case ESM::fourCC("XCVR"): // FO4 + case ESM::fourCC("XFVC"): // FO4 + case ESM::fourCC("XHTW"): // FO4 + case ESM::fourCC("XIS2"): // FO4 + case ESM::fourCC("XLOD"): // FO4 + case ESM::fourCC("XLRL"): // FO4 + case ESM::fourCC("XLRT"): // FO4 + case ESM::fourCC("XLYR"): // FO4 + case ESM::fourCC("XMSP"): // FO4 + case ESM::fourCC("XRFG"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadpwat.cpp b/components/esm4/loadpwat.cpp index 339ae63daf..33a2c86546 100644 --- a/components/esm4/loadpwat.cpp +++ b/components/esm4/loadpwat.cpp @@ -43,12 +43,12 @@ void ESM4::PlaceableWater::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_OBND: - case ESM4::SUB_MODL: - case ESM4::SUB_DNAM: + case ESM::fourCC("OBND"): + case ESM::fourCC("MODL"): + case ESM::fourCC("DNAM"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadqust.cpp b/components/esm4/loadqust.cpp index b7f9b33db9..27c23d92f1 100644 --- a/components/esm4/loadqust.cpp +++ b/components/esm4/loadqust.cpp @@ -42,16 +42,16 @@ void ESM4::Quest::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getZString(mQuestName); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mFileName); break; // TES4 (none in FO3/FONV) - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (subHdr.dataSize == 2) // TES4 { @@ -66,10 +66,10 @@ void ESM4::Quest::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mQuestScript); break; - case ESM4::SUB_CTDA: // FIXME: how to detect if 1st/2nd param is a formid? + case ESM::fourCC("CTDA"): // FIXME: how to detect if 1st/2nd param is a formid? { if (subHdr.dataSize == 24) // TES4 { @@ -95,80 +95,80 @@ void ESM4::Quest::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCHR: + case ESM::fourCC("SCHR"): reader.get(mScript.scriptHeader); break; - case ESM4::SUB_SCDA: + case ESM::fourCC("SCDA"): reader.skipSubRecordData(); break; // compiled script data - case ESM4::SUB_SCTX: + case ESM::fourCC("SCTX"): reader.getString(mScript.scriptSource); break; - case ESM4::SUB_SCRO: + case ESM::fourCC("SCRO"): reader.getFormId(mScript.globReference); break; - case ESM4::SUB_INDX: - case ESM4::SUB_QSDT: - case ESM4::SUB_CNAM: - case ESM4::SUB_QSTA: - case ESM4::SUB_NNAM: // FO3 - case ESM4::SUB_QOBJ: // FO3 - case ESM4::SUB_NAM0: // FO3 - case ESM4::SUB_ANAM: // TES5 - case ESM4::SUB_DNAM: // TES5 - case ESM4::SUB_ENAM: // TES5 - case ESM4::SUB_FNAM: // TES5 - case ESM4::SUB_NEXT: // TES5 - case ESM4::SUB_ALCA: // TES5 - case ESM4::SUB_ALCL: // TES5 - case ESM4::SUB_ALCO: // TES5 - case ESM4::SUB_ALDN: // TES5 - case ESM4::SUB_ALEA: // TES5 - case ESM4::SUB_ALED: // TES5 - case ESM4::SUB_ALEQ: // TES5 - case ESM4::SUB_ALFA: // TES5 - case ESM4::SUB_ALFC: // TES5 - case ESM4::SUB_ALFD: // TES5 - case ESM4::SUB_ALFE: // TES5 - case ESM4::SUB_ALFI: // TES5 - case ESM4::SUB_ALFL: // TES5 - case ESM4::SUB_ALFR: // TES5 - case ESM4::SUB_ALID: // TES5 - case ESM4::SUB_ALLS: // TES5 - case ESM4::SUB_ALNA: // TES5 - case ESM4::SUB_ALNT: // TES5 - case ESM4::SUB_ALPC: // TES5 - case ESM4::SUB_ALRT: // TES5 - case ESM4::SUB_ALSP: // TES5 - case ESM4::SUB_ALST: // TES5 - case ESM4::SUB_ALUA: // TES5 - case ESM4::SUB_CIS1: // TES5 - case ESM4::SUB_CIS2: // TES5 - case ESM4::SUB_CNTO: // TES5 - case ESM4::SUB_COCT: // TES5 - case ESM4::SUB_ECOR: // TES5 - case ESM4::SUB_FLTR: // TES5 - case ESM4::SUB_KNAM: // TES5 - case ESM4::SUB_KSIZ: // TES5 - case ESM4::SUB_KWDA: // TES5 - case ESM4::SUB_QNAM: // TES5 - case ESM4::SUB_QTGL: // TES5 - case ESM4::SUB_SPOR: // TES5 - case ESM4::SUB_VMAD: // TES5 - case ESM4::SUB_VTCK: // TES5 - case ESM4::SUB_ALCC: // FO4 - case ESM4::SUB_ALCS: // FO4 - case ESM4::SUB_ALDI: // FO4 - case ESM4::SUB_ALFV: // FO4 - case ESM4::SUB_ALLA: // FO4 - case ESM4::SUB_ALMI: // FO4 - case ESM4::SUB_GNAM: // FO4 - case ESM4::SUB_GWOR: // FO4 - case ESM4::SUB_LNAM: // FO4 - case ESM4::SUB_NAM2: // FO4 - case ESM4::SUB_OCOR: // FO4 - case ESM4::SUB_SNAM: // FO4 - case ESM4::SUB_XNAM: // FO4 + case ESM::fourCC("INDX"): + case ESM::fourCC("QSDT"): + case ESM::fourCC("CNAM"): + case ESM::fourCC("QSTA"): + case ESM::fourCC("NNAM"): // FO3 + case ESM::fourCC("QOBJ"): // FO3 + case ESM::fourCC("NAM0"): // FO3 + case ESM::fourCC("ANAM"): // TES5 + case ESM::fourCC("DNAM"): // TES5 + case ESM::fourCC("ENAM"): // TES5 + case ESM::fourCC("FNAM"): // TES5 + case ESM::fourCC("NEXT"): // TES5 + case ESM::fourCC("ALCA"): // TES5 + case ESM::fourCC("ALCL"): // TES5 + case ESM::fourCC("ALCO"): // TES5 + case ESM::fourCC("ALDN"): // TES5 + case ESM::fourCC("ALEA"): // TES5 + case ESM::fourCC("ALED"): // TES5 + case ESM::fourCC("ALEQ"): // TES5 + case ESM::fourCC("ALFA"): // TES5 + case ESM::fourCC("ALFC"): // TES5 + case ESM::fourCC("ALFD"): // TES5 + case ESM::fourCC("ALFE"): // TES5 + case ESM::fourCC("ALFI"): // TES5 + case ESM::fourCC("ALFL"): // TES5 + case ESM::fourCC("ALFR"): // TES5 + case ESM::fourCC("ALID"): // TES5 + case ESM::fourCC("ALLS"): // TES5 + case ESM::fourCC("ALNA"): // TES5 + case ESM::fourCC("ALNT"): // TES5 + case ESM::fourCC("ALPC"): // TES5 + case ESM::fourCC("ALRT"): // TES5 + case ESM::fourCC("ALSP"): // TES5 + case ESM::fourCC("ALST"): // TES5 + case ESM::fourCC("ALUA"): // TES5 + case ESM::fourCC("CIS1"): // TES5 + case ESM::fourCC("CIS2"): // TES5 + case ESM::fourCC("CNTO"): // TES5 + case ESM::fourCC("COCT"): // TES5 + case ESM::fourCC("ECOR"): // TES5 + case ESM::fourCC("FLTR"): // TES5 + case ESM::fourCC("KNAM"): // TES5 + case ESM::fourCC("KSIZ"): // TES5 + case ESM::fourCC("KWDA"): // TES5 + case ESM::fourCC("QNAM"): // TES5 + case ESM::fourCC("QTGL"): // TES5 + case ESM::fourCC("SPOR"): // TES5 + case ESM::fourCC("VMAD"): // TES5 + case ESM::fourCC("VTCK"): // TES5 + case ESM::fourCC("ALCC"): // FO4 + case ESM::fourCC("ALCS"): // FO4 + case ESM::fourCC("ALDI"): // FO4 + case ESM::fourCC("ALFV"): // FO4 + case ESM::fourCC("ALLA"): // FO4 + case ESM::fourCC("ALMI"): // FO4 + case ESM::fourCC("GNAM"): // FO4 + case ESM::fourCC("GWOR"): // FO4 + case ESM::fourCC("LNAM"): // FO4 + case ESM::fourCC("NAM2"): // FO4 + case ESM::fourCC("OCOR"): // FO4 + case ESM::fourCC("SNAM"): // FO4 + case ESM::fourCC("XNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadrace.cpp b/components/esm4/loadrace.cpp index 7434a7f87f..02f6f953b4 100644 --- a/components/esm4/loadrace.cpp +++ b/components/esm4/loadrace.cpp @@ -52,7 +52,7 @@ void ESM4::Race::load(ESM4::Reader& reader) // std::cout << "RACE " << ESM::printName(subHdr.typeId) << std::endl; switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): { reader.getZString(mEditorId); // TES4 @@ -73,10 +73,10 @@ void ESM4::Race::load(ESM4::Reader& reader) // Imperial 0x00000907 break; } - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): { if (subHdr.dataSize == 1) // FO3? { @@ -87,10 +87,10 @@ void ESM4::Race::load(ESM4::Reader& reader) reader.getLocalizedString(mDesc); break; } - case ESM4::SUB_SPLO: // bonus spell formid (TES5 may have SPCT and multiple SPLO) + case ESM::fourCC("SPLO"): // bonus spell formid (TES5 may have SPCT and multiple SPLO) reader.getFormId(mBonusSpells.emplace_back()); break; - case ESM4::SUB_DATA: // ?? different length for TES5 + case ESM::fourCC("DATA"): // ?? different length for TES5 { // DATA:size 128 // 0f 0f ff 00 ff 00 ff 00 ff 00 ff 00 ff 00 00 00 @@ -210,14 +210,14 @@ void ESM4::Race::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): { reader.getFormId(mDefaultHair[0]); // male reader.getFormId(mDefaultHair[1]); // female break; } - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): // CNAM SNAM VNAM // Sheogorath 0x0 0000 98 2b 10011000 00101011 // Golden Saint 0x3 0011 26 46 00100110 01000110 @@ -238,13 +238,13 @@ void ESM4::Race::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mFaceGenMainClamp); break; // 0x40A00000 = 5.f - case ESM4::SUB_UNAM: + case ESM::fourCC("UNAM"): reader.get(mFaceGenFaceClamp); break; // 0x40400000 = 3.f - case ESM4::SUB_ATTR: // Only in TES4? + case ESM::fourCC("ATTR"): // Only in TES4? { if (subHdr.dataSize == 2) // FO3? { @@ -276,7 +276,7 @@ void ESM4::Race::load(ESM4::Reader& reader) // | | // +-------------+ // - case ESM4::SUB_NAM0: // start marker head data /* 1 */ + case ESM::fourCC("NAM0"): // start marker head data /* 1 */ { curr_part = 0; // head part @@ -296,7 +296,7 @@ void ESM4::Race::load(ESM4::Reader& reader) currentIndex = 0xffffffff; break; } - case ESM4::SUB_INDX: + case ESM::fourCC("INDX"): { reader.get(currentIndex); // FIXME: below check is rather useless @@ -313,7 +313,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): { if (currentIndex == 0xffffffff) { @@ -350,10 +350,10 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.skipSubRecordData(); break; // always 0x0000? - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): { if (currentIndex == 0xffffffff) { @@ -379,7 +379,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } // - case ESM4::SUB_NAM1: // start marker body data /* 4 */ + case ESM::fourCC("NAM1"): // start marker body data /* 4 */ { if (isFO3 || isFONV) @@ -406,14 +406,14 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): isMale = true; break; /* 2, 5, 7 */ - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): isMale = false; break; /* 3, 6, 8 */ // - case ESM4::SUB_HNAM: + case ESM::fourCC("HNAM"): { // FIXME: this is a texture name in FO4 if (subHdr.dataSize % sizeof(ESM::FormId32) != 0) @@ -428,7 +428,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): { std::size_t numEyeChoices = subHdr.dataSize / sizeof(ESM::FormId32); mEyeChoices.resize(numEyeChoices); @@ -437,7 +437,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGGS: + case ESM::fourCC("FGGS"): { if (isMale || isTES4) { @@ -454,7 +454,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGGA: + case ESM::fourCC("FGGA"): { if (isMale || isTES4) { @@ -471,7 +471,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_FGTS: + case ESM::fourCC("FGTS"): { if (isMale || isTES4) { @@ -489,12 +489,12 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } // - case ESM4::SUB_SNAM: // skipping...2 // only in TES4? + case ESM::fourCC("SNAM"): // skipping...2 // only in TES4? { reader.skipSubRecordData(); break; } - case ESM4::SUB_XNAM: + case ESM::fourCC("XNAM"): { ESM::FormId race; std::int32_t adjustment; @@ -504,7 +504,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_VNAM: + case ESM::fourCC("VNAM"): { if (subHdr.dataSize == 8) // TES4 { @@ -528,7 +528,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } // - case ESM4::SUB_ANAM: // TES5 + case ESM::fourCC("ANAM"): // TES5 { if (isMale) reader.getZString(mModelMale); @@ -536,10 +536,10 @@ void ESM4::Race::load(ESM4::Reader& reader) reader.getZString(mModelFemale); break; } - case ESM4::SUB_KSIZ: + case ESM::fourCC("KSIZ"): reader.get(mNumKeywords); break; - case ESM4::SUB_KWDA: + case ESM::fourCC("KWDA"): { ESM::FormId formid; for (unsigned int i = 0; i < mNumKeywords; ++i) @@ -547,13 +547,13 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } // - case ESM4::SUB_WNAM: // ARMO FormId + case ESM::fourCC("WNAM"): // ARMO FormId { reader.getFormId(mSkin); // std::cout << mEditorId << " skin " << formIdToString(mSkin) << std::endl; // FIXME break; } - case ESM4::SUB_BODT: // body template + case ESM::fourCC("BODT"): // body template { reader.get(mBodyTemplate.bodyPart); reader.get(mBodyTemplate.flags); @@ -564,7 +564,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_BOD2: + case ESM::fourCC("BOD2"): { if (subHdr.dataSize == 8 || subHdr.dataSize == 4) // TES5, FO4 { @@ -584,7 +584,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_HEAD: // TES5 + case ESM::fourCC("HEAD"): // TES5 { ESM::FormId formId; reader.getFormId(formId); @@ -611,7 +611,7 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_NAM3: // start of hkx model + case ESM::fourCC("NAM3"): // start of hkx model { curr_part = 3; // for TES5 NAM3 indicates the start of hkx model @@ -651,7 +651,7 @@ void ESM4::Race::load(ESM4::Reader& reader) // ManakinRace // ManakinRace // ManakinRace FX0 - case ESM4::SUB_NAME: // TES5 biped object names (x32) + case ESM::fourCC("NAME"): // TES5 biped object names (x32) { std::string name; reader.getZString(name); @@ -659,112 +659,112 @@ void ESM4::Race::load(ESM4::Reader& reader) break; } - case ESM4::SUB_MTNM: // movement type - case ESM4::SUB_ATKD: // attack data - case ESM4::SUB_ATKE: // attach event - case ESM4::SUB_GNAM: // body part data - case ESM4::SUB_NAM4: // material type - case ESM4::SUB_NAM5: // unarmed impact? - case ESM4::SUB_LNAM: // close loot sound - case ESM4::SUB_QNAM: // equipment slot formid - case ESM4::SUB_HCLF: // default hair colour - case ESM4::SUB_UNES: // unarmed equipment slot formid - case ESM4::SUB_TINC: - case ESM4::SUB_TIND: - case ESM4::SUB_TINI: - case ESM4::SUB_TINL: - case ESM4::SUB_TINP: - case ESM4::SUB_TINT: - case ESM4::SUB_TINV: - case ESM4::SUB_TIRS: - case ESM4::SUB_PHWT: - case ESM4::SUB_AHCF: - case ESM4::SUB_AHCM: - case ESM4::SUB_MPAI: - case ESM4::SUB_MPAV: - case ESM4::SUB_DFTF: - case ESM4::SUB_DFTM: - case ESM4::SUB_FLMV: - case ESM4::SUB_FTSF: - case ESM4::SUB_FTSM: - case ESM4::SUB_MTYP: - case ESM4::SUB_NAM7: - case ESM4::SUB_NAM8: - case ESM4::SUB_PHTN: - case ESM4::SUB_RNAM: - case ESM4::SUB_RNMV: - case ESM4::SUB_RPRF: - case ESM4::SUB_RPRM: - case ESM4::SUB_SNMV: - case ESM4::SUB_SPCT: - case ESM4::SUB_SPED: - case ESM4::SUB_SWMV: - case ESM4::SUB_WKMV: - case ESM4::SUB_SPMV: - case ESM4::SUB_ATKR: - case ESM4::SUB_CTDA: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end + case ESM::fourCC("MTNM"): // movement type + case ESM::fourCC("ATKD"): // attack data + case ESM::fourCC("ATKE"): // attach event + case ESM::fourCC("GNAM"): // body part data + case ESM::fourCC("NAM4"): // material type + case ESM::fourCC("NAM5"): // unarmed impact? + case ESM::fourCC("LNAM"): // close loot sound + case ESM::fourCC("QNAM"): // equipment slot formid + case ESM::fourCC("HCLF"): // default hair colour + case ESM::fourCC("UNES"): // unarmed equipment slot formid + case ESM::fourCC("TINC"): + case ESM::fourCC("TIND"): + case ESM::fourCC("TINI"): + case ESM::fourCC("TINL"): + case ESM::fourCC("TINP"): + case ESM::fourCC("TINT"): + case ESM::fourCC("TINV"): + case ESM::fourCC("TIRS"): + case ESM::fourCC("PHWT"): + case ESM::fourCC("AHCF"): + case ESM::fourCC("AHCM"): + case ESM::fourCC("MPAI"): + case ESM::fourCC("MPAV"): + case ESM::fourCC("DFTF"): + case ESM::fourCC("DFTM"): + case ESM::fourCC("FLMV"): + case ESM::fourCC("FTSF"): + case ESM::fourCC("FTSM"): + case ESM::fourCC("MTYP"): + case ESM::fourCC("NAM7"): + case ESM::fourCC("NAM8"): + case ESM::fourCC("PHTN"): + case ESM::fourCC("RNAM"): + case ESM::fourCC("RNMV"): + case ESM::fourCC("RPRF"): + case ESM::fourCC("RPRM"): + case ESM::fourCC("SNMV"): + case ESM::fourCC("SPCT"): + case ESM::fourCC("SPED"): + case ESM::fourCC("SWMV"): + case ESM::fourCC("WKMV"): + case ESM::fourCC("SPMV"): + case ESM::fourCC("ATKR"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end // - case ESM4::SUB_YNAM: // FO3 - case ESM4::SUB_NAM2: // FO3 - case ESM4::SUB_VTCK: // FO3 - case ESM4::SUB_MODD: // FO3 - case ESM4::SUB_ONAM: // FO3 - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_ATKS: // FO4 - case ESM4::SUB_ATKT: // FO4 - case ESM4::SUB_ATKW: // FO4 - case ESM4::SUB_BMMP: // FO4 - case ESM4::SUB_BSMB: // FO4 - case ESM4::SUB_BSMP: // FO4 - case ESM4::SUB_BSMS: // FO4 - - case ESM4::SUB_FMRI: // FO4 - case ESM4::SUB_FMRN: // FO4 - case ESM4::SUB_HLTX: // FO4 - case ESM4::SUB_MLSI: // FO4 - case ESM4::SUB_MPGN: // FO4 - case ESM4::SUB_MPGS: // FO4 - case ESM4::SUB_MPPC: // FO4 - case ESM4::SUB_MPPF: // FO4 - case ESM4::SUB_MPPI: // FO4 - case ESM4::SUB_MPPK: // FO4 - case ESM4::SUB_MPPM: // FO4 - case ESM4::SUB_MPPN: // FO4 - case ESM4::SUB_MPPT: // FO4 - case ESM4::SUB_MSID: // FO4 - case ESM4::SUB_MSM0: // FO4 - case ESM4::SUB_MSM1: // FO4 - case ESM4::SUB_NNAM: // FO4 - case ESM4::SUB_NTOP: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTOP: // FO4 - case ESM4::SUB_QSTI: // FO4 - case ESM4::SUB_RBPC: // FO4 - case ESM4::SUB_SADD: // FO4 - case ESM4::SUB_SAKD: // FO4 - case ESM4::SUB_SAPT: // FO4 - case ESM4::SUB_SGNM: // FO4 - case ESM4::SUB_SRAC: // FO4 - case ESM4::SUB_SRAF: // FO4 - case ESM4::SUB_STCP: // FO4 - case ESM4::SUB_STKD: // FO4 - case ESM4::SUB_TETI: // FO4 - case ESM4::SUB_TTEB: // FO4 - case ESM4::SUB_TTEC: // FO4 - case ESM4::SUB_TTED: // FO4 - case ESM4::SUB_TTEF: // FO4 - case ESM4::SUB_TTET: // FO4 - case ESM4::SUB_TTGE: // FO4 - case ESM4::SUB_TTGP: // FO4 - case ESM4::SUB_UNWP: // FO4 - case ESM4::SUB_WMAP: // FO4 - case ESM4::SUB_ZNAM: // FO4 + case ESM::fourCC("YNAM"): // FO3 + case ESM::fourCC("NAM2"): // FO3 + case ESM::fourCC("VTCK"): // FO3 + case ESM::fourCC("MODD"): // FO3 + case ESM::fourCC("ONAM"): // FO3 + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("ATKS"): // FO4 + case ESM::fourCC("ATKT"): // FO4 + case ESM::fourCC("ATKW"): // FO4 + case ESM::fourCC("BMMP"): // FO4 + case ESM::fourCC("BSMB"): // FO4 + case ESM::fourCC("BSMP"): // FO4 + case ESM::fourCC("BSMS"): // FO4 + + case ESM::fourCC("FMRI"): // FO4 + case ESM::fourCC("FMRN"): // FO4 + case ESM::fourCC("HLTX"): // FO4 + case ESM::fourCC("MLSI"): // FO4 + case ESM::fourCC("MPGN"): // FO4 + case ESM::fourCC("MPGS"): // FO4 + case ESM::fourCC("MPPC"): // FO4 + case ESM::fourCC("MPPF"): // FO4 + case ESM::fourCC("MPPI"): // FO4 + case ESM::fourCC("MPPK"): // FO4 + case ESM::fourCC("MPPM"): // FO4 + case ESM::fourCC("MPPN"): // FO4 + case ESM::fourCC("MPPT"): // FO4 + case ESM::fourCC("MSID"): // FO4 + case ESM::fourCC("MSM0"): // FO4 + case ESM::fourCC("MSM1"): // FO4 + case ESM::fourCC("NNAM"): // FO4 + case ESM::fourCC("NTOP"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTOP"): // FO4 + case ESM::fourCC("QSTI"): // FO4 + case ESM::fourCC("RBPC"): // FO4 + case ESM::fourCC("SADD"): // FO4 + case ESM::fourCC("SAKD"): // FO4 + case ESM::fourCC("SAPT"): // FO4 + case ESM::fourCC("SGNM"): // FO4 + case ESM::fourCC("SRAC"): // FO4 + case ESM::fourCC("SRAF"): // FO4 + case ESM::fourCC("STCP"): // FO4 + case ESM::fourCC("STKD"): // FO4 + case ESM::fourCC("TETI"): // FO4 + case ESM::fourCC("TTEB"): // FO4 + case ESM::fourCC("TTEC"): // FO4 + case ESM::fourCC("TTED"): // FO4 + case ESM::fourCC("TTEF"): // FO4 + case ESM::fourCC("TTET"): // FO4 + case ESM::fourCC("TTGE"): // FO4 + case ESM::fourCC("TTGP"): // FO4 + case ESM::fourCC("UNWP"): // FO4 + case ESM::fourCC("WMAP"): // FO4 + case ESM::fourCC("ZNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadrefr.cpp b/components/esm4/loadrefr.cpp index fb26e39546..5ac3a5f077 100644 --- a/components/esm4/loadrefr.cpp +++ b/components/esm4/loadrefr.cpp @@ -46,26 +46,26 @@ void ESM4::Reference::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_NAME: + case ESM::fourCC("NAME"): { ESM::FormId BaseId; reader.getFormId(BaseId); mBaseObj = BaseId; break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mPos); break; - case ESM4::SUB_XSCL: + case ESM::fourCC("XSCL"): reader.get(mScale); break; - case ESM4::SUB_XOWN: + case ESM::fourCC("XOWN"): { switch (subHdr.dataSize) { @@ -86,13 +86,13 @@ void ESM4::Reference::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XGLB: + case ESM::fourCC("XGLB"): reader.getFormId(mGlobal); break; - case ESM4::SUB_XRNK: + case ESM::fourCC("XRNK"): reader.get(mFactionRank); break; - case ESM4::SUB_XESP: + case ESM::fourCC("XESP"): { reader.getFormId(mEsp.parent); reader.get(mEsp.flags); @@ -100,7 +100,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) // << ", 0x" << std::hex << (mEsp.flags & 0xff) << std::endl;// FIXME break; } - case ESM4::SUB_XTEL: + case ESM::fourCC("XTEL"): { switch (subHdr.dataSize) { @@ -125,7 +125,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_XSED: + case ESM::fourCC("XSED"): { // 1 or 4 bytes if (subHdr.dataSize == 1) @@ -147,7 +147,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_XLOD: + case ESM::fourCC("XLOD"): { // 12 bytes if (subHdr.dataSize == 12) @@ -168,7 +168,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_XACT: + case ESM::fourCC("XACT"): { if (subHdr.dataSize == 4) { @@ -182,7 +182,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_XRTM: // formId + case ESM::fourCC("XRTM"): // formId { // seems like another ref, e.g. 00064583 has base object 00000034 which is "XMarkerHeading" // e.g. some are doors (prob. quest related) @@ -199,7 +199,7 @@ void ESM4::Reference::load(ESM4::Reader& reader) // std::cout << "REFR " << mEditorId << " XRTM : " << formIdToString(marker) << std::endl;// FIXME break; } - case ESM4::SUB_TNAM: // reader.get(mMapMarker); break; + case ESM::fourCC("TNAM"): // reader.get(mMapMarker); break; { if (subHdr.dataSize != sizeof(mMapMarker)) // reader.skipSubRecordData(); // FIXME: FO3 @@ -209,26 +209,26 @@ void ESM4::Reference::load(ESM4::Reader& reader) break; } - case ESM4::SUB_XMRK: + case ESM::fourCC("XMRK"): mIsMapMarker = true; break; // all have mBaseObj 0x00000010 "MapMarker" - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): { // std::cout << "REFR " << ESM::printName(subHdr.typeId) << " skipping..." // << subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } - case ESM4::SUB_XTRG: // formId + case ESM::fourCC("XTRG"): // formId { reader.getFormId(mTargetRef); // std::cout << "REFR XRTG : " << formIdToString(id) << std::endl;// FIXME break; } - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getFormId(mAudioLocation); break; // FONV - case ESM4::SUB_XRDO: // FO3 + case ESM::fourCC("XRDO"): // FO3 { // FIXME: completely different meaning in FO4 reader.get(mRadio.rangeRadius); @@ -238,14 +238,14 @@ void ESM4::Reference::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCRO: // FO3 + case ESM::fourCC("SCRO"): // FO3 { reader.getFormId(sid); // if (mFormId == 0x0016b74B) // std::cout << "REFR SCRO : " << formIdToString(sid) << std::endl;// FIXME break; } - case ESM4::SUB_XLOC: + case ESM::fourCC("XLOC"): { mIsLocked = true; std::int8_t dummy; // FIXME: very poor code @@ -268,97 +268,97 @@ void ESM4::Reference::load(ESM4::Reader& reader) break; } - case ESM4::SUB_XCNT: + case ESM::fourCC("XCNT"): { reader.get(mCount); break; } // lighting - case ESM4::SUB_LNAM: // lighting template formId - case ESM4::SUB_XLIG: // struct, FOV, fade, etc - case ESM4::SUB_XEMI: // LIGH formId - case ESM4::SUB_XRDS: // Radius or Radiance - case ESM4::SUB_XRGB: - case ESM4::SUB_XRGD: // tangent data? - case ESM4::SUB_XALP: // alpha cutoff + case ESM::fourCC("LNAM"): // lighting template formId + case ESM::fourCC("XLIG"): // struct, FOV, fade, etc + case ESM::fourCC("XEMI"): // LIGH formId + case ESM::fourCC("XRDS"): // Radius or Radiance + case ESM::fourCC("XRGB"): + case ESM::fourCC("XRGD"): // tangent data? + case ESM::fourCC("XALP"): // alpha cutoff // - case ESM4::SUB_XPCI: // formId - case ESM4::SUB_XLCM: - case ESM4::SUB_ONAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_XPRM: - case ESM4::SUB_INAM: - case ESM4::SUB_PDTO: - case ESM4::SUB_SCHR: - case ESM4::SUB_SCTX: - case ESM4::SUB_XAPD: - case ESM4::SUB_XAPR: - case ESM4::SUB_XCVL: - case ESM4::SUB_XCZA: - case ESM4::SUB_XCZC: - case ESM4::SUB_XEZN: - case ESM4::SUB_XFVC: - case ESM4::SUB_XHTW: - case ESM4::SUB_XIS2: - case ESM4::SUB_XLCN: - case ESM4::SUB_XLIB: - case ESM4::SUB_XLKR: - case ESM4::SUB_XLRM: - case ESM4::SUB_XLRT: - case ESM4::SUB_XLTW: - case ESM4::SUB_XMBO: - case ESM4::SUB_XMBP: - case ESM4::SUB_XMBR: - case ESM4::SUB_XNDP: - case ESM4::SUB_XOCP: - case ESM4::SUB_XPOD: - case ESM4::SUB_XPTL: - case ESM4::SUB_XPPA: - case ESM4::SUB_XPRD: - case ESM4::SUB_XPWR: - case ESM4::SUB_XRMR: - case ESM4::SUB_XSPC: - case ESM4::SUB_XTNM: - case ESM4::SUB_XTRI: - case ESM4::SUB_XWCN: - case ESM4::SUB_XWCU: - case ESM4::SUB_XATR: - case ESM4::SUB_XHLT: // Unofficial Oblivion Patch - case ESM4::SUB_XCHG: // thievery.exp - case ESM4::SUB_XHLP: // FO3 - case ESM4::SUB_XAMT: // FO3 - case ESM4::SUB_XAMC: // FO3 - case ESM4::SUB_XRAD: // FO3 - case ESM4::SUB_XIBS: // FO3 - case ESM4::SUB_XORD: // FO3 - case ESM4::SUB_XCLP: // FO3 - case ESM4::SUB_SCDA: // FO3 - case ESM4::SUB_RCLR: // FO3 - case ESM4::SUB_BNAM: // FONV - case ESM4::SUB_MMRK: // FONV - case ESM4::SUB_MNAM: // FONV - case ESM4::SUB_NNAM: // FONV - case ESM4::SUB_XATO: // FONV - case ESM4::SUB_SCRV: // FONV - case ESM4::SUB_SCVR: // FONV - case ESM4::SUB_SLSD: // FONV - case ESM4::SUB_XSRF: // FONV - case ESM4::SUB_XSRD: // FONV - case ESM4::SUB_WMI1: // FONV - case ESM4::SUB_XLRL: // Unofficial Skyrim Patch - case ESM4::SUB_XASP: // FO4 - case ESM4::SUB_XATP: // FO4 - case ESM4::SUB_XBSD: // FO4 - case ESM4::SUB_XCVR: // FO4 - case ESM4::SUB_XCZR: // FO4 - case ESM4::SUB_XLKT: // FO4 - case ESM4::SUB_XLYR: // FO4 - case ESM4::SUB_XMSP: // FO4 - case ESM4::SUB_XPDD: // FO4 - case ESM4::SUB_XPLK: // FO4 - case ESM4::SUB_XRFG: // FO4 - case ESM4::SUB_XWPG: // FO4 - case ESM4::SUB_XWPN: // FO4 + case ESM::fourCC("XPCI"): // formId + case ESM::fourCC("XLCM"): + case ESM::fourCC("ONAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("XPRM"): + case ESM::fourCC("INAM"): + case ESM::fourCC("PDTO"): + case ESM::fourCC("SCHR"): + case ESM::fourCC("SCTX"): + case ESM::fourCC("XAPD"): + case ESM::fourCC("XAPR"): + case ESM::fourCC("XCVL"): + case ESM::fourCC("XCZA"): + case ESM::fourCC("XCZC"): + case ESM::fourCC("XEZN"): + case ESM::fourCC("XFVC"): + case ESM::fourCC("XHTW"): + case ESM::fourCC("XIS2"): + case ESM::fourCC("XLCN"): + case ESM::fourCC("XLIB"): + case ESM::fourCC("XLKR"): + case ESM::fourCC("XLRM"): + case ESM::fourCC("XLRT"): + case ESM::fourCC("XLTW"): + case ESM::fourCC("XMBO"): + case ESM::fourCC("XMBP"): + case ESM::fourCC("XMBR"): + case ESM::fourCC("XNDP"): + case ESM::fourCC("XOCP"): + case ESM::fourCC("XPOD"): + case ESM::fourCC("XPTL"): + case ESM::fourCC("XPPA"): + case ESM::fourCC("XPRD"): + case ESM::fourCC("XPWR"): + case ESM::fourCC("XRMR"): + case ESM::fourCC("XSPC"): + case ESM::fourCC("XTNM"): + case ESM::fourCC("XTRI"): + case ESM::fourCC("XWCN"): + case ESM::fourCC("XWCU"): + case ESM::fourCC("XATR"): + case ESM::fourCC("XHLT"): // Unofficial Oblivion Patch + case ESM::fourCC("XCHG"): // thievery.exp + case ESM::fourCC("XHLP"): // FO3 + case ESM::fourCC("XAMT"): // FO3 + case ESM::fourCC("XAMC"): // FO3 + case ESM::fourCC("XRAD"): // FO3 + case ESM::fourCC("XIBS"): // FO3 + case ESM::fourCC("XORD"): // FO3 + case ESM::fourCC("XCLP"): // FO3 + case ESM::fourCC("SCDA"): // FO3 + case ESM::fourCC("RCLR"): // FO3 + case ESM::fourCC("BNAM"): // FONV + case ESM::fourCC("MMRK"): // FONV + case ESM::fourCC("MNAM"): // FONV + case ESM::fourCC("NNAM"): // FONV + case ESM::fourCC("XATO"): // FONV + case ESM::fourCC("SCRV"): // FONV + case ESM::fourCC("SCVR"): // FONV + case ESM::fourCC("SLSD"): // FONV + case ESM::fourCC("XSRF"): // FONV + case ESM::fourCC("XSRD"): // FONV + case ESM::fourCC("WMI1"): // FONV + case ESM::fourCC("XLRL"): // Unofficial Skyrim Patch + case ESM::fourCC("XASP"): // FO4 + case ESM::fourCC("XATP"): // FO4 + case ESM::fourCC("XBSD"): // FO4 + case ESM::fourCC("XCVR"): // FO4 + case ESM::fourCC("XCZR"): // FO4 + case ESM::fourCC("XLKT"): // FO4 + case ESM::fourCC("XLYR"): // FO4 + case ESM::fourCC("XMSP"): // FO4 + case ESM::fourCC("XPDD"): // FO4 + case ESM::fourCC("XPLK"): // FO4 + case ESM::fourCC("XRFG"): // FO4 + case ESM::fourCC("XWPG"): // FO4 + case ESM::fourCC("XWPN"): // FO4 // if (mFormId == 0x0007e90f) // XPRM XPOD // if (mBaseObj == 0x17) //XPRM XOCP occlusion plane data XMBO bound half extents reader.skipSubRecordData(); diff --git a/components/esm4/loadregn.cpp b/components/esm4/loadregn.cpp index 2f10ea22d8..c8cc9663d9 100644 --- a/components/esm4/loadregn.cpp +++ b/components/esm4/loadregn.cpp @@ -41,22 +41,22 @@ void ESM4::Region::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_RCLR: + case ESM::fourCC("RCLR"): reader.get(mColour); break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): reader.getFormId(mWorldId); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mShader); break; - case ESM4::SUB_RPLI: + case ESM::fourCC("RPLI"): reader.get(mEdgeFalloff); break; - case ESM4::SUB_RPLD: + case ESM::fourCC("RPLD"): { mRPLD.resize(subHdr.dataSize / sizeof(std::uint32_t)); for (std::vector::iterator it = mRPLD.begin(); it != mRPLD.end(); ++it) @@ -71,10 +71,10 @@ void ESM4::Region::load(ESM4::Reader& reader) break; } - case ESM4::SUB_RDAT: + case ESM::fourCC("RDAT"): reader.get(mData); break; - case ESM4::SUB_RDMP: + case ESM::fourCC("RDMP"): { if (mData.type != RDAT_Map) throw std::runtime_error("REGN unexpected data type"); @@ -83,7 +83,7 @@ void ESM4::Region::load(ESM4::Reader& reader) } // FO3 only 2: DemoMegatonSound and DC01 (both 0 RDMD) // FONV none - case ESM4::SUB_RDMD: // music type; 0 default, 1 public, 2 dungeon + case ESM::fourCC("RDMD"): // music type; 0 default, 1 public, 2 dungeon { #if 0 int dummy; @@ -94,14 +94,14 @@ void ESM4::Region::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_RDMO: // not seen in FO3/FONV? + case ESM::fourCC("RDMO"): // not seen in FO3/FONV? { // std::cout << "REGN " << ESM::printName(subHdr.typeId) << " skipping..." // << subHdr.dataSize << std::endl; reader.skipSubRecordData(); break; } - case ESM4::SUB_RDSD: // Possibly the same as RDSA + case ESM::fourCC("RDSD"): // Possibly the same as RDSA { if (mData.type != RDAT_Sound) throw std::runtime_error( @@ -114,16 +114,16 @@ void ESM4::Region::load(ESM4::Reader& reader) break; } - case ESM4::SUB_RDGS: // Only in Oblivion? (ToddTestRegion1) // formId - case ESM4::SUB_RDSA: - case ESM4::SUB_RDWT: // formId - case ESM4::SUB_RDOT: // formId - case ESM4::SUB_RDID: // FONV - case ESM4::SUB_RDSB: // FONV - case ESM4::SUB_RDSI: // FONV - case ESM4::SUB_NVMI: // TES5 - case ESM4::SUB_ANAM: // FO4 - case ESM4::SUB_RLDM: // FO4 + case ESM::fourCC("RDGS"): // Only in Oblivion? (ToddTestRegion1) // formId + case ESM::fourCC("RDSA"): + case ESM::fourCC("RDWT"): // formId + case ESM::fourCC("RDOT"): // formId + case ESM::fourCC("RDID"): // FONV + case ESM::fourCC("RDSB"): // FONV + case ESM::fourCC("RDSI"): // FONV + case ESM::fourCC("NVMI"): // TES5 + case ESM::fourCC("ANAM"): // FO4 + case ESM::fourCC("RLDM"): // FO4 // RDAT skipping... following is a map // RDMP skipping... map name // diff --git a/components/esm4/loadroad.cpp b/components/esm4/loadroad.cpp index 8a33ab1c1d..3e33acbc7b 100644 --- a/components/esm4/loadroad.cpp +++ b/components/esm4/loadroad.cpp @@ -45,7 +45,7 @@ void ESM4::Road::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_PGRP: + case ESM::fourCC("PGRP"): { std::size_t numNodes = subHdr.dataSize / sizeof(PGRP); @@ -57,7 +57,7 @@ void ESM4::Road::load(ESM4::Reader& reader) break; } - case ESM4::SUB_PGRR: + case ESM::fourCC("PGRR"): { static PGRR link; static RDRP linkPt; diff --git a/components/esm4/loadsbsp.cpp b/components/esm4/loadsbsp.cpp index a874331dab..c9450492b8 100644 --- a/components/esm4/loadsbsp.cpp +++ b/components/esm4/loadsbsp.cpp @@ -41,10 +41,10 @@ void ESM4::SubSpace::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): { reader.get(mDimension.x); reader.get(mDimension.y); diff --git a/components/esm4/loadscol.cpp b/components/esm4/loadscol.cpp index dea900fe17..00775edaa5 100644 --- a/components/esm4/loadscol.cpp +++ b/components/esm4/loadscol.cpp @@ -43,22 +43,22 @@ void ESM4::StaticCollection::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_OBND: - case ESM4::SUB_MODL: // Model data start - case ESM4::SUB_MODT: - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_ONAM: - case ESM4::SUB_DATA: - case ESM4::SUB_FLTR: // FO4 - case ESM4::SUB_PTRN: // FO4 + case ESM::fourCC("OBND"): + case ESM::fourCC("MODL"): // Model data start + case ESM::fourCC("MODT"): + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("ONAM"): + case ESM::fourCC("DATA"): + case ESM::fourCC("FLTR"): // FO4 + case ESM::fourCC("PTRN"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadscpt.cpp b/components/esm4/loadscpt.cpp index b4071ed21d..12953b4609 100644 --- a/components/esm4/loadscpt.cpp +++ b/components/esm4/loadscpt.cpp @@ -43,12 +43,12 @@ void ESM4::Script::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): { reader.getZString(mEditorId); break; } - case ESM4::SUB_SCHR: + case ESM::fourCC("SCHR"): { // For debugging only #if 0 @@ -73,12 +73,12 @@ void ESM4::Script::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_SCTX: + case ESM::fourCC("SCTX"): reader.getString(mScript.scriptSource); // if (mEditorId == "CTrapLogs01SCRIPT") // std::cout << mScript.scriptSource << std::endl; break; - case ESM4::SUB_SCDA: // compiled script data + case ESM::fourCC("SCDA"): // compiled script data { // For debugging only #if 0 @@ -112,10 +112,10 @@ void ESM4::Script::load(ESM4::Reader& reader) #endif break; } - case ESM4::SUB_SCRO: + case ESM::fourCC("SCRO"): reader.getFormId(mScript.globReference); break; - case ESM4::SUB_SLSD: + case ESM::fourCC("SLSD"): { localVar.clear(); reader.get(localVar.index); @@ -128,11 +128,11 @@ void ESM4::Script::load(ESM4::Reader& reader) break; } - case ESM4::SUB_SCVR: // assumed always pair with SLSD + case ESM::fourCC("SCVR"): // assumed always pair with SLSD reader.getZString(localVar.variableName); mScript.localVarData.push_back(localVar); break; - case ESM4::SUB_SCRV: + case ESM::fourCC("SCRV"): { std::uint32_t index; reader.get(index); diff --git a/components/esm4/loadscrl.cpp b/components/esm4/loadscrl.cpp index 954ddf4e36..f88854f8db 100644 --- a/components/esm4/loadscrl.cpp +++ b/components/esm4/loadscrl.cpp @@ -41,40 +41,40 @@ void ESM4::Scroll::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData.value); reader.get(mData.weight); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - // case ESM4::SUB_MODB: reader.get(mBoundRadius); break; - case ESM4::SUB_OBND: - case ESM4::SUB_CTDA: - case ESM4::SUB_EFID: - case ESM4::SUB_EFIT: - case ESM4::SUB_ETYP: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_MDOB: - case ESM4::SUB_MODT: - case ESM4::SUB_SPIT: - case ESM4::SUB_CIS2: + // case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; + case ESM::fourCC("OBND"): + case ESM::fourCC("CTDA"): + case ESM::fourCC("EFID"): + case ESM::fourCC("EFIT"): + case ESM::fourCC("ETYP"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("MDOB"): + case ESM::fourCC("MODT"): + case ESM::fourCC("SPIT"): + case ESM::fourCC("CIS2"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadsgst.cpp b/components/esm4/loadsgst.cpp index c4284eb9bc..d93709f537 100644 --- a/components/esm4/loadsgst.cpp +++ b/components/esm4/loadsgst.cpp @@ -42,10 +42,10 @@ void ESM4::SigilStone::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): { if (mFullName.empty()) { @@ -62,34 +62,34 @@ void ESM4::SigilStone::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { reader.get(mData.uses); reader.get(mData.value); reader.get(mData.weight); break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_SCIT: + case ESM::fourCC("SCIT"): { reader.get(mEffect); reader.adjustFormId(mEffect.formId); break; } - case ESM4::SUB_MODT: - case ESM4::SUB_EFID: - case ESM4::SUB_EFIT: + case ESM::fourCC("MODT"): + case ESM::fourCC("EFID"): + case ESM::fourCC("EFIT"): { reader.skipSubRecordData(); break; diff --git a/components/esm4/loadslgm.cpp b/components/esm4/loadslgm.cpp index 635b73312e..cb16f36857 100644 --- a/components/esm4/loadslgm.cpp +++ b/components/esm4/loadslgm.cpp @@ -41,38 +41,38 @@ void ESM4::SoulGem::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mData); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_SOUL: + case ESM::fourCC("SOUL"): reader.get(mSoul); break; - case ESM4::SUB_SLCP: + case ESM::fourCC("SLCP"): reader.get(mSoulCapacity); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_NAM0: - case ESM4::SUB_OBND: + case ESM::fourCC("MODT"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("NAM0"): + case ESM::fourCC("OBND"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadsndr.cpp b/components/esm4/loadsndr.cpp index 830cdfdc54..21ed9f93e4 100644 --- a/components/esm4/loadsndr.cpp +++ b/components/esm4/loadsndr.cpp @@ -41,10 +41,10 @@ void ESM4::SoundReference::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_CTDA: + case ESM::fourCC("CTDA"): reader.get(&mTargetCondition, 20); reader.get(mTargetCondition.runOn); reader.get(mTargetCondition.reference); @@ -52,22 +52,22 @@ void ESM4::SoundReference::load(ESM4::Reader& reader) reader.adjustFormId(mTargetCondition.reference); reader.skipSubRecordData(4); // unknown break; - case ESM4::SUB_GNAM: + case ESM::fourCC("GNAM"): reader.getFormId(mSoundCategory); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mSoundId); break; - case ESM4::SUB_ONAM: + case ESM::fourCC("ONAM"): reader.getFormId(mOutputModel); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.getZString(mSoundFile); break; - case ESM4::SUB_LNAM: + case ESM::fourCC("LNAM"): reader.get(mLoopInfo); break; - case ESM4::SUB_BNAM: + case ESM::fourCC("BNAM"): { if (subHdr.dataSize == 6) reader.get(mData); @@ -77,16 +77,16 @@ void ESM4::SoundReference::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; } - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_CNAM: // CRC32 hash - case ESM4::SUB_DNAM: // FO4 - case ESM4::SUB_FNAM: // unknown - case ESM4::SUB_INTV: // FO4 - case ESM4::SUB_ITMC: // FO4 - case ESM4::SUB_ITME: // FO4 - case ESM4::SUB_ITMS: // FO4 - case ESM4::SUB_NNAM: // FO4 + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("CNAM"): // CRC32 hash + case ESM::fourCC("DNAM"): // FO4 + case ESM::fourCC("FNAM"): // unknown + case ESM::fourCC("INTV"): // FO4 + case ESM::fourCC("ITMC"): // FO4 + case ESM::fourCC("ITME"): // FO4 + case ESM::fourCC("ITMS"): // FO4 + case ESM::fourCC("NNAM"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadsoun.cpp b/components/esm4/loadsoun.cpp index a5ae005fe2..4fc3b6609f 100644 --- a/components/esm4/loadsoun.cpp +++ b/components/esm4/loadsoun.cpp @@ -41,16 +41,16 @@ void ESM4::Sound::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FNAM: + case ESM::fourCC("FNAM"): reader.getZString(mSoundFile); break; - case ESM4::SUB_SNDX: + case ESM::fourCC("SNDX"): reader.get(mData); break; - case ESM4::SUB_SNDD: + case ESM::fourCC("SNDD"): if (subHdr.dataSize == 8) reader.get(&mData, 8); else @@ -59,13 +59,13 @@ void ESM4::Sound::load(ESM4::Reader& reader) reader.get(mExtra); } break; - case ESM4::SUB_OBND: // TES5 only - case ESM4::SUB_SDSC: // TES5 only - case ESM4::SUB_ANAM: // FO3 - case ESM4::SUB_GNAM: // FO3 - case ESM4::SUB_HNAM: // FO3 - case ESM4::SUB_RNAM: // FONV - case ESM4::SUB_REPT: // FO4 + case ESM::fourCC("OBND"): // TES5 only + case ESM::fourCC("SDSC"): // TES5 only + case ESM::fourCC("ANAM"): // FO3 + case ESM::fourCC("GNAM"): // FO3 + case ESM::fourCC("HNAM"): // FO3 + case ESM::fourCC("RNAM"): // FONV + case ESM::fourCC("REPT"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadstat.cpp b/components/esm4/loadstat.cpp index e3b51633cf..9d85374ae4 100644 --- a/components/esm4/loadstat.cpp +++ b/components/esm4/loadstat.cpp @@ -41,19 +41,19 @@ void ESM4::Static::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: + case ESM::fourCC("MODT"): { // version is only availabe in TES5 (seems to be 27 or 28?) // if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) @@ -72,7 +72,7 @@ void ESM4::Static::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): { for (std::string& level : mLOD) { @@ -84,18 +84,18 @@ void ESM4::Static::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MODC: // More model data - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_DNAM: - case ESM4::SUB_BRUS: // FONV - case ESM4::SUB_RNAM: // FONV - case ESM4::SUB_FTYP: // FO4 - case ESM4::SUB_NVNM: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_VMAD: // FO4 + case ESM::fourCC("MODC"): // More model data + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("DNAM"): + case ESM::fourCC("BRUS"): // FONV + case ESM::fourCC("RNAM"): // FONV + case ESM::fourCC("FTYP"): // FO4 + case ESM::fourCC("NVNM"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("VMAD"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadtact.cpp b/components/esm4/loadtact.cpp index ad5efb9fbf..453df85504 100644 --- a/components/esm4/loadtact.cpp +++ b/components/esm4/loadtact.cpp @@ -41,44 +41,44 @@ void ESM4::TalkingActivator::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_VNAM: + case ESM::fourCC("VNAM"): reader.getFormId(mVoiceType); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getFormId(mLoopSound); break; - case ESM4::SUB_INAM: + case ESM::fourCC("INAM"): reader.getFormId(mRadioTemplate); break; // FONV - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_FNAM: - case ESM4::SUB_PNAM: - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("FNAM"): + case ESM::fourCC("PNAM"): + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadterm.cpp b/components/esm4/loadterm.cpp index 39f26391e8..1fb8ef117d 100644 --- a/components/esm4/loadterm.cpp +++ b/components/esm4/loadterm.cpp @@ -41,73 +41,73 @@ void ESM4::Terminal::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.getFormId(mPasswordNote); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): if (subHdr.dataSize == 4) reader.getFormId(mSound); // FIXME: FO4 sound marker params else reader.skipSubRecordData(); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_RNAM: + case ESM::fourCC("RNAM"): reader.getZString(mResultText); break; - case ESM4::SUB_DNAM: // difficulty - case ESM4::SUB_ANAM: // flags - case ESM4::SUB_CTDA: - case ESM4::SUB_CIS1: - case ESM4::SUB_CIS2: - case ESM4::SUB_INAM: - case ESM4::SUB_ITXT: // Menu Item - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_SCDA: - case ESM4::SUB_SCHR: - case ESM4::SUB_SCRO: - case ESM4::SUB_SCRV: - case ESM4::SUB_SCTX: - case ESM4::SUB_SCVR: - case ESM4::SUB_SLSD: - case ESM4::SUB_TNAM: - case ESM4::SUB_OBND: - case ESM4::SUB_VMAD: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_BSIZ: // FO4 - case ESM4::SUB_BTXT: // FO4 - case ESM4::SUB_COCT: // FO4 - case ESM4::SUB_CNTO: // FO4 - case ESM4::SUB_FNAM: // FO4 - case ESM4::SUB_ISIZ: // FO4 - case ESM4::SUB_ITID: // FO4 - case ESM4::SUB_MNAM: // FO4 - case ESM4::SUB_NAM0: // FO4 - case ESM4::SUB_PRPS: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_UNAM: // FO4 - case ESM4::SUB_VNAM: // FO4 - case ESM4::SUB_WBDT: // FO4 - case ESM4::SUB_WNAM: // FO4 - case ESM4::SUB_XMRK: // FO4 + case ESM::fourCC("DNAM"): // difficulty + case ESM::fourCC("ANAM"): // flags + case ESM::fourCC("CTDA"): + case ESM::fourCC("CIS1"): + case ESM::fourCC("CIS2"): + case ESM::fourCC("INAM"): + case ESM::fourCC("ITXT"): // Menu Item + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("SCDA"): + case ESM::fourCC("SCHR"): + case ESM::fourCC("SCRO"): + case ESM::fourCC("SCRV"): + case ESM::fourCC("SCTX"): + case ESM::fourCC("SCVR"): + case ESM::fourCC("SLSD"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("OBND"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("BSIZ"): // FO4 + case ESM::fourCC("BTXT"): // FO4 + case ESM::fourCC("COCT"): // FO4 + case ESM::fourCC("CNTO"): // FO4 + case ESM::fourCC("FNAM"): // FO4 + case ESM::fourCC("ISIZ"): // FO4 + case ESM::fourCC("ITID"): // FO4 + case ESM::fourCC("MNAM"): // FO4 + case ESM::fourCC("NAM0"): // FO4 + case ESM::fourCC("PRPS"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("UNAM"): // FO4 + case ESM::fourCC("VNAM"): // FO4 + case ESM::fourCC("WBDT"): // FO4 + case ESM::fourCC("WNAM"): // FO4 + case ESM::fourCC("XMRK"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadtes4.cpp b/components/esm4/loadtes4.cpp index 0cbf91c52e..19db9b9d09 100644 --- a/components/esm4/loadtes4.cpp +++ b/components/esm4/loadtes4.cpp @@ -41,7 +41,7 @@ void ESM4::Header::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_HEDR: + case ESM::fourCC("HEDR"): { if (!reader.getExact(mData.version) || !reader.getExact(mData.records) || !reader.getExact(mData.nextObjectId)) @@ -51,13 +51,13 @@ void ESM4::Header::load(ESM4::Reader& reader) throw std::runtime_error("TES4 HEDR data size mismatch"); break; } - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getZString(mAuthor); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.getZString(mDesc); break; - case ESM4::SUB_MAST: // multiple + case ESM::fourCC("MAST"): // multiple { ESM::MasterData m; if (!reader.getZString(m.name)) @@ -68,7 +68,7 @@ void ESM4::Header::load(ESM4::Reader& reader) mMaster.push_back(m); break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { if (mMaster.empty()) throw std::runtime_error( @@ -78,7 +78,7 @@ void ESM4::Header::load(ESM4::Reader& reader) throw std::runtime_error("TES4 DATA data read error"); break; } - case ESM4::SUB_ONAM: + case ESM::fourCC("ONAM"): { mOverrides.resize(subHdr.dataSize / sizeof(ESM::FormId32)); for (ESM::FormId& mOverride : mOverrides) @@ -95,11 +95,11 @@ void ESM4::Header::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_INTV: - case ESM4::SUB_INCC: - case ESM4::SUB_OFST: // Oblivion only? - case ESM4::SUB_DELE: // Oblivion only? - case ESM4::SUB_TNAM: // Fallout 4 (CK only) + case ESM::fourCC("INTV"): + case ESM::fourCC("INCC"): + case ESM::fourCC("OFST"): // Oblivion only? + case ESM::fourCC("DELE"): // Oblivion only? + case ESM::fourCC("TNAM"): // Fallout 4 (CK only) case ESM::fourCC("MMSB"): // Fallout 76 reader.skipSubRecordData(); break; diff --git a/components/esm4/loadtree.cpp b/components/esm4/loadtree.cpp index 9290ae79c4..c433f11564 100644 --- a/components/esm4/loadtree.cpp +++ b/components/esm4/loadtree.cpp @@ -41,29 +41,29 @@ void ESM4::Tree::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mLeafTexture); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_CNAM: - case ESM4::SUB_BNAM: - case ESM4::SUB_SNAM: - case ESM4::SUB_FULL: - case ESM4::SUB_OBND: - case ESM4::SUB_PFIG: - case ESM4::SUB_PFPC: + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("CNAM"): + case ESM::fourCC("BNAM"): + case ESM::fourCC("SNAM"): + case ESM::fourCC("FULL"): + case ESM::fourCC("OBND"): + case ESM::fourCC("PFIG"): + case ESM::fourCC("PFPC"): reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadtxst.cpp b/components/esm4/loadtxst.cpp index 3b5f04f265..69d48cc049 100644 --- a/components/esm4/loadtxst.cpp +++ b/components/esm4/loadtxst.cpp @@ -41,37 +41,37 @@ void ESM4::TextureSet::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; case ESM::fourCC("FLTR"): // FO76 reader.getZString(mFilter); break; - case ESM4::SUB_TX00: + case ESM::fourCC("TX00"): reader.getZString(mDiffuse); break; - case ESM4::SUB_TX01: + case ESM::fourCC("TX01"): reader.getZString(mNormalMap); break; - case ESM4::SUB_TX02: + case ESM::fourCC("TX02"): // This is a "wrinkle map" in FO4/76 reader.getZString(mEnvMask); break; - case ESM4::SUB_TX03: + case ESM::fourCC("TX03"): // This is a glow map in FO4/76 reader.getZString(mToneMap); break; - case ESM4::SUB_TX04: + case ESM::fourCC("TX04"): // This is a height map in FO4/76 reader.getZString(mDetailMap); break; - case ESM4::SUB_TX05: + case ESM::fourCC("TX05"): reader.getZString(mEnvMap); break; - case ESM4::SUB_TX06: + case ESM::fourCC("TX06"): reader.getZString(mMultiLayer); break; - case ESM4::SUB_TX07: + case ESM::fourCC("TX07"): // This is a "smooth specular" map in FO4/76 reader.getZString(mSpecular); break; @@ -84,14 +84,14 @@ void ESM4::TextureSet::load(ESM4::Reader& reader) case ESM::fourCC("TX10"): // FO76 reader.getZString(mFlow); break; - case ESM4::SUB_DNAM: + case ESM::fourCC("DNAM"): reader.get(mDataFlags); break; - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): reader.getZString(mMaterial); break; - case ESM4::SUB_DODT: // Decal data - case ESM4::SUB_OBND: // object bounds + case ESM::fourCC("DODT"): // Decal data + case ESM::fourCC("OBND"): // object bounds case ESM::fourCC("OPDS"): // Object placement defaults, FO76 reader.skipSubRecordData(); break; diff --git a/components/esm4/loadweap.cpp b/components/esm4/loadweap.cpp index 2b80305690..81b0d4286a 100644 --- a/components/esm4/loadweap.cpp +++ b/components/esm4/loadweap.cpp @@ -43,13 +43,13 @@ void ESM4::Weapon::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): { // if (reader.esmVersion() == ESM::VER_094 || reader.esmVersion() == ESM::VER_170) if (subHdr.dataSize == 10) // FO3 has 15 bytes even though VER_094 @@ -79,126 +79,126 @@ void ESM4::Weapon::load(ESM4::Reader& reader) } break; } - case ESM4::SUB_MODL: + case ESM::fourCC("MODL"): reader.getZString(mModel); break; - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mIcon); break; - case ESM4::SUB_MICO: + case ESM::fourCC("MICO"): reader.getZString(mMiniIcon); break; // FO3 - case ESM4::SUB_SCRI: + case ESM::fourCC("SCRI"): reader.getFormId(mScriptId); break; - case ESM4::SUB_ANAM: + case ESM::fourCC("ANAM"): reader.get(mEnchantmentPoints); break; - case ESM4::SUB_ENAM: + case ESM::fourCC("ENAM"): reader.getFormId(mEnchantment); break; - case ESM4::SUB_MODB: + case ESM::fourCC("MODB"): reader.get(mBoundRadius); break; - case ESM4::SUB_DESC: + case ESM::fourCC("DESC"): reader.getLocalizedString(mText); break; - case ESM4::SUB_YNAM: + case ESM::fourCC("YNAM"): reader.getFormId(mPickUpSound); break; - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mDropSound); break; - case ESM4::SUB_MODT: // Model data - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_BAMT: - case ESM4::SUB_BIDS: - case ESM4::SUB_INAM: - case ESM4::SUB_CNAM: - case ESM4::SUB_CRDT: - case ESM4::SUB_DNAM: - case ESM4::SUB_EAMT: - case ESM4::SUB_EITM: - case ESM4::SUB_ETYP: - case ESM4::SUB_KSIZ: - case ESM4::SUB_KWDA: - case ESM4::SUB_NAM8: - case ESM4::SUB_NAM9: - case ESM4::SUB_OBND: - case ESM4::SUB_SNAM: - case ESM4::SUB_TNAM: - case ESM4::SUB_UNAM: - case ESM4::SUB_VMAD: - case ESM4::SUB_VNAM: - case ESM4::SUB_WNAM: - case ESM4::SUB_XNAM: // Dawnguard only? - case ESM4::SUB_NNAM: - case ESM4::SUB_NAM0: // FO3 - case ESM4::SUB_REPL: // FO3 - case ESM4::SUB_MOD2: // FO3 - case ESM4::SUB_MO2T: // FO3 - case ESM4::SUB_MO2S: // FO3 - case ESM4::SUB_NAM6: // FO3 - case ESM4::SUB_MOD4: // First person model data - case ESM4::SUB_MO4T: - case ESM4::SUB_MO4S: - case ESM4::SUB_MO4C: - case ESM4::SUB_MO4F: // First person model data end - case ESM4::SUB_BIPL: // FO3 - case ESM4::SUB_NAM7: // FO3 - case ESM4::SUB_MOD3: // FO3 - case ESM4::SUB_MO3T: // FO3 - case ESM4::SUB_MO3S: // FO3 - case ESM4::SUB_MODD: // FO3 - // case ESM4::SUB_MOSD: // FO3 - case ESM4::SUB_DAMC: // Destructible - case ESM4::SUB_DEST: - case ESM4::SUB_DMDC: - case ESM4::SUB_DMDL: - case ESM4::SUB_DMDT: - case ESM4::SUB_DMDS: - case ESM4::SUB_DSTA: - case ESM4::SUB_DSTD: - case ESM4::SUB_DSTF: // Destructible end - case ESM4::SUB_VATS: // FONV - case ESM4::SUB_VANM: // FONV - case ESM4::SUB_MWD1: // FONV - case ESM4::SUB_MWD2: // FONV - case ESM4::SUB_MWD3: // FONV - case ESM4::SUB_MWD4: // FONV - case ESM4::SUB_MWD5: // FONV - case ESM4::SUB_MWD6: // FONV - case ESM4::SUB_MWD7: // FONV - case ESM4::SUB_WMI1: // FONV - case ESM4::SUB_WMI2: // FONV - case ESM4::SUB_WMI3: // FONV - case ESM4::SUB_WMS1: // FONV - case ESM4::SUB_WMS2: // FONV - case ESM4::SUB_WNM1: // FONV - case ESM4::SUB_WNM2: // FONV - case ESM4::SUB_WNM3: // FONV - case ESM4::SUB_WNM4: // FONV - case ESM4::SUB_WNM5: // FONV - case ESM4::SUB_WNM6: // FONV - case ESM4::SUB_WNM7: // FONV - case ESM4::SUB_EFSD: // FONV DeadMoney - case ESM4::SUB_APPR: // FO4 - case ESM4::SUB_DAMA: // FO4 - case ESM4::SUB_FLTR: // FO4 - case ESM4::SUB_FNAM: // FO4 - case ESM4::SUB_INRD: // FO4 - case ESM4::SUB_LNAM: // FO4 - case ESM4::SUB_MASE: // FO4 - case ESM4::SUB_PTRN: // FO4 - case ESM4::SUB_STCP: // FO4 - case ESM4::SUB_WAMD: // FO4 - case ESM4::SUB_WZMD: // FO4 - case ESM4::SUB_OBTE: // FO4 object template start - case ESM4::SUB_OBTF: - case ESM4::SUB_OBTS: - case ESM4::SUB_STOP: // FO4 object template end + case ESM::fourCC("MODT"): // Model data + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("BAMT"): + case ESM::fourCC("BIDS"): + case ESM::fourCC("INAM"): + case ESM::fourCC("CNAM"): + case ESM::fourCC("CRDT"): + case ESM::fourCC("DNAM"): + case ESM::fourCC("EAMT"): + case ESM::fourCC("EITM"): + case ESM::fourCC("ETYP"): + case ESM::fourCC("KSIZ"): + case ESM::fourCC("KWDA"): + case ESM::fourCC("NAM8"): + case ESM::fourCC("NAM9"): + case ESM::fourCC("OBND"): + case ESM::fourCC("SNAM"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("UNAM"): + case ESM::fourCC("VMAD"): + case ESM::fourCC("VNAM"): + case ESM::fourCC("WNAM"): + case ESM::fourCC("XNAM"): // Dawnguard only? + case ESM::fourCC("NNAM"): + case ESM::fourCC("NAM0"): // FO3 + case ESM::fourCC("REPL"): // FO3 + case ESM::fourCC("MOD2"): // FO3 + case ESM::fourCC("MO2T"): // FO3 + case ESM::fourCC("MO2S"): // FO3 + case ESM::fourCC("NAM6"): // FO3 + case ESM::fourCC("MOD4"): // First person model data + case ESM::fourCC("MO4T"): + case ESM::fourCC("MO4S"): + case ESM::fourCC("MO4C"): + case ESM::fourCC("MO4F"): // First person model data end + case ESM::fourCC("BIPL"): // FO3 + case ESM::fourCC("NAM7"): // FO3 + case ESM::fourCC("MOD3"): // FO3 + case ESM::fourCC("MO3T"): // FO3 + case ESM::fourCC("MO3S"): // FO3 + case ESM::fourCC("MODD"): // FO3 + // case ESM::fourCC("MOSD"): // FO3 + case ESM::fourCC("DAMC"): // Destructible + case ESM::fourCC("DEST"): + case ESM::fourCC("DMDC"): + case ESM::fourCC("DMDL"): + case ESM::fourCC("DMDT"): + case ESM::fourCC("DMDS"): + case ESM::fourCC("DSTA"): + case ESM::fourCC("DSTD"): + case ESM::fourCC("DSTF"): // Destructible end + case ESM::fourCC("VATS"): // FONV + case ESM::fourCC("VANM"): // FONV + case ESM::fourCC("MWD1"): // FONV + case ESM::fourCC("MWD2"): // FONV + case ESM::fourCC("MWD3"): // FONV + case ESM::fourCC("MWD4"): // FONV + case ESM::fourCC("MWD5"): // FONV + case ESM::fourCC("MWD6"): // FONV + case ESM::fourCC("MWD7"): // FONV + case ESM::fourCC("WMI1"): // FONV + case ESM::fourCC("WMI2"): // FONV + case ESM::fourCC("WMI3"): // FONV + case ESM::fourCC("WMS1"): // FONV + case ESM::fourCC("WMS2"): // FONV + case ESM::fourCC("WNM1"): // FONV + case ESM::fourCC("WNM2"): // FONV + case ESM::fourCC("WNM3"): // FONV + case ESM::fourCC("WNM4"): // FONV + case ESM::fourCC("WNM5"): // FONV + case ESM::fourCC("WNM6"): // FONV + case ESM::fourCC("WNM7"): // FONV + case ESM::fourCC("EFSD"): // FONV DeadMoney + case ESM::fourCC("APPR"): // FO4 + case ESM::fourCC("DAMA"): // FO4 + case ESM::fourCC("FLTR"): // FO4 + case ESM::fourCC("FNAM"): // FO4 + case ESM::fourCC("INRD"): // FO4 + case ESM::fourCC("LNAM"): // FO4 + case ESM::fourCC("MASE"): // FO4 + case ESM::fourCC("PTRN"): // FO4 + case ESM::fourCC("STCP"): // FO4 + case ESM::fourCC("WAMD"): // FO4 + case ESM::fourCC("WZMD"): // FO4 + case ESM::fourCC("OBTE"): // FO4 object template start + case ESM::fourCC("OBTF"): + case ESM::fourCC("OBTS"): + case ESM::fourCC("STOP"): // FO4 object template end reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadwrld.cpp b/components/esm4/loadwrld.cpp index b29cb37eb5..d9bae15385 100644 --- a/components/esm4/loadwrld.cpp +++ b/components/esm4/loadwrld.cpp @@ -56,46 +56,46 @@ void ESM4::World::load(ESM4::Reader& reader) const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); switch (subHdr.typeId) { - case ESM4::SUB_EDID: + case ESM::fourCC("EDID"): reader.getZString(mEditorId); break; - case ESM4::SUB_FULL: + case ESM::fourCC("FULL"): reader.getLocalizedString(mFullName); break; - case ESM4::SUB_WCTR: // TES5+ + case ESM::fourCC("WCTR"): // TES5+ reader.get(mCenterCell); break; - case ESM4::SUB_WNAM: + case ESM::fourCC("WNAM"): reader.getFormId(mParent); break; - case ESM4::SUB_SNAM: + case ESM::fourCC("SNAM"): reader.get(mSound); break; // sound, Oblivion only? - case ESM4::SUB_ICON: + case ESM::fourCC("ICON"): reader.getZString(mMapFile); break; - case ESM4::SUB_CNAM: + case ESM::fourCC("CNAM"): reader.getFormId(mClimate); break; - case ESM4::SUB_NAM2: + case ESM::fourCC("NAM2"): reader.getFormId(mWater); break; - case ESM4::SUB_NAM0: + case ESM::fourCC("NAM0"): { reader.get(mMinX); reader.get(mMinY); break; } - case ESM4::SUB_NAM9: + case ESM::fourCC("NAM9"): { reader.get(mMaxX); reader.get(mMaxY); break; } - case ESM4::SUB_DATA: + case ESM::fourCC("DATA"): reader.get(mWorldFlags); break; - case ESM4::SUB_MNAM: + case ESM::fourCC("MNAM"): { reader.get(mMap.width); reader.get(mMap.height); @@ -113,7 +113,7 @@ void ESM4::World::load(ESM4::Reader& reader) break; } - case ESM4::SUB_DNAM: // defaults + case ESM::fourCC("DNAM"): // defaults { reader.get(mLandLevel); // -2700.f for TES5 reader.get(mWaterLevel); // -14000.f for TES5 @@ -135,37 +135,37 @@ void ESM4::World::load(ESM4::Reader& reader) // 00119D2E freeside\freeside_01.mp3 0012D94D FreesideNorthWorld (Freeside) // 00119D2E freeside\freeside_01.mp3 0012D94E FreesideFortWorld (Old Mormon Fort) // NOTE: FONV DefaultObjectManager has 00090908 "explore" as the default music - case ESM4::SUB_ZNAM: + case ESM::fourCC("ZNAM"): reader.getFormId(mMusic); break; - case ESM4::SUB_PNAM: + case ESM::fourCC("PNAM"): reader.get(mParentUseFlags); break; - case ESM4::SUB_OFST: - case ESM4::SUB_RNAM: // multiple - case ESM4::SUB_MHDT: - case ESM4::SUB_LTMP: - case ESM4::SUB_XEZN: - case ESM4::SUB_XLCN: - case ESM4::SUB_NAM3: - case ESM4::SUB_NAM4: - case ESM4::SUB_NAMA: - case ESM4::SUB_ONAM: - case ESM4::SUB_TNAM: - case ESM4::SUB_UNAM: - case ESM4::SUB_XWEM: - case ESM4::SUB_MODL: // Model data start - case ESM4::SUB_MODT: - case ESM4::SUB_MODC: - case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_INAM: // FO3 - case ESM4::SUB_NNAM: // FO3 - case ESM4::SUB_XNAM: // FO3 - case ESM4::SUB_IMPS: // FO3 Anchorage - case ESM4::SUB_IMPF: // FO3 Anchorage - case ESM4::SUB_CLSZ: // FO4 - case ESM4::SUB_WLEV: // FO4 + case ESM::fourCC("OFST"): + case ESM::fourCC("RNAM"): // multiple + case ESM::fourCC("MHDT"): + case ESM::fourCC("LTMP"): + case ESM::fourCC("XEZN"): + case ESM::fourCC("XLCN"): + case ESM::fourCC("NAM3"): + case ESM::fourCC("NAM4"): + case ESM::fourCC("NAMA"): + case ESM::fourCC("ONAM"): + case ESM::fourCC("TNAM"): + case ESM::fourCC("UNAM"): + case ESM::fourCC("XWEM"): + case ESM::fourCC("MODL"): // Model data start + case ESM::fourCC("MODT"): + case ESM::fourCC("MODC"): + case ESM::fourCC("MODS"): + case ESM::fourCC("MODF"): // Model data end + case ESM::fourCC("INAM"): // FO3 + case ESM::fourCC("NNAM"): // FO3 + case ESM::fourCC("XNAM"): // FO3 + case ESM::fourCC("IMPS"): // FO3 Anchorage + case ESM::fourCC("IMPF"): // FO3 Anchorage + case ESM::fourCC("CLSZ"): // FO4 + case ESM::fourCC("WLEV"): // FO4 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp index a3ea438d65..9811cf6103 100644 --- a/components/esm4/reader.cpp +++ b/components/esm4/reader.cpp @@ -588,7 +588,7 @@ namespace ESM4 // Extended storage subrecord redefines the following subrecord's size. // Would need to redesign the loader to support that, so skip over both subrecords. - if (result && mCtx.subRecordHeader.typeId == ESM4::SUB_XXXX) + if (result && mCtx.subRecordHeader.typeId == ESM::fourCC("XXXX")) { std::uint32_t extDataSize; get(extDataSize); diff --git a/components/esm4/reader.hpp b/components/esm4/reader.hpp index 92dc00b96d..914fa4a647 100644 --- a/components/esm4/reader.hpp +++ b/components/esm4/reader.hpp @@ -335,7 +335,7 @@ namespace ESM4 // Get a subrecord of a particular type and data type template - bool getSubRecord(const ESM4::SubRecordTypes type, T& t) + bool getSubRecord(const std::uint32_t type, T& t) { ESM4::SubRecordHeader hdr; if (!getExact(hdr) || (hdr.typeId != type) || (hdr.dataSize != sizeof(T))) From 974415addf059fc37b41a7ee054c1440ed8090d8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 15 Mar 2024 09:57:36 +0300 Subject: [PATCH 279/451] Allow weapon equip/unequip animations to intersect (#7886) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/character.cpp | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a89eaacaaa..2d276774b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -158,6 +158,7 @@ Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs Bug #7859: AutoCalc flag is not used to calculate potion value Bug #7872: Region sounds use wrong odds + Bug #7886: Equip and unequip animations can't share the animation track section Bug #7887: Editor: Mismatched reported script data size and actual data size causes a crash during save Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c0f6111a79..91daaa1fd1 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1060,17 +1060,23 @@ namespace MWMechanics std::string_view action = evt.substr(groupname.size() + 2); if (action == "equip attach") { - if (groupname == "shield") - mAnimation->showCarriedLeft(true); - else - mAnimation->showWeapons(true); + if (mUpperBodyState == UpperBodyState::Equipping) + { + if (groupname == "shield") + mAnimation->showCarriedLeft(true); + else + mAnimation->showWeapons(true); + } } else if (action == "unequip detach") { - if (groupname == "shield") - mAnimation->showCarriedLeft(false); - else - mAnimation->showWeapons(false); + if (mUpperBodyState == UpperBodyState::Unequipping) + { + if (groupname == "shield") + mAnimation->showCarriedLeft(false); + else + mAnimation->showWeapons(false); + } } else if (action == "chop hit" || action == "slash hit" || action == "thrust hit" || action == "hit") { From 0da8b29a8830b06062420dddcc2fb3eabb4657d4 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 19 Mar 2024 22:14:53 +0100 Subject: [PATCH 280/451] Remove static modifier from local variables used to store temporary loading results They make the code thread unsafe because different threads will use the same memory to write and read using different instances of the loaded objects. --- components/esm4/loadcont.cpp | 2 +- components/esm4/loadcrea.cpp | 2 +- components/esm4/loadinfo.cpp | 2 +- components/esm4/loadlvlc.cpp | 2 +- components/esm4/loadlvli.cpp | 2 +- components/esm4/loadlvln.cpp | 2 +- components/esm4/loadnpc.cpp | 2 +- components/esm4/loadpack.cpp | 2 +- components/esm4/loadpgrd.cpp | 4 ++-- components/esm4/loadroad.cpp | 4 ++-- components/esm4/loadscpt.cpp | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/components/esm4/loadcont.cpp b/components/esm4/loadcont.cpp index d650678093..7c90472d29 100644 --- a/components/esm4/loadcont.cpp +++ b/components/esm4/loadcont.cpp @@ -53,7 +53,7 @@ void ESM4::Container::load(ESM4::Reader& reader) break; case ESM4::SUB_CNTO: { - static InventoryItem inv; // FIXME: use unique_ptr here? + InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); reader.adjustFormId(inv.item); mInventory.push_back(inv); diff --git a/components/esm4/loadcrea.cpp b/components/esm4/loadcrea.cpp index 0c07eb92e3..cb587b091d 100644 --- a/components/esm4/loadcrea.cpp +++ b/components/esm4/loadcrea.cpp @@ -56,7 +56,7 @@ void ESM4::Creature::load(ESM4::Reader& reader) break; case ESM4::SUB_CNTO: { - static InventoryItem inv; // FIXME: use unique_ptr here? + InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); reader.adjustFormId(inv.item); mInventory.push_back(inv); diff --git a/components/esm4/loadinfo.cpp b/components/esm4/loadinfo.cpp index 1b001c1665..1acc419ada 100644 --- a/components/esm4/loadinfo.cpp +++ b/components/esm4/loadinfo.cpp @@ -41,7 +41,7 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) mEditorId = ESM::RefId(mId).serializeText(); // FIXME: quick workaround to use existing code - static ScriptLocalVariableData localVar; + ScriptLocalVariableData localVar; bool ignore = false; while (reader.getSubRecordHeader()) diff --git a/components/esm4/loadlvlc.cpp b/components/esm4/loadlvlc.cpp index b1a0a0f241..8f045b6038 100644 --- a/components/esm4/loadlvlc.cpp +++ b/components/esm4/loadlvlc.cpp @@ -58,7 +58,7 @@ void ESM4::LevelledCreature::load(ESM4::Reader& reader) break; case ESM4::SUB_LVLO: { - static LVLO lvlo; + LVLO lvlo; if (subHdr.dataSize != 12) { if (subHdr.dataSize == 8) diff --git a/components/esm4/loadlvli.cpp b/components/esm4/loadlvli.cpp index cab8db4a21..6253db5272 100644 --- a/components/esm4/loadlvli.cpp +++ b/components/esm4/loadlvli.cpp @@ -56,7 +56,7 @@ void ESM4::LevelledItem::load(ESM4::Reader& reader) break; case ESM4::SUB_LVLO: { - static LVLO lvlo; + LVLO lvlo; if (subHdr.dataSize != 12) { if (subHdr.dataSize == 8) diff --git a/components/esm4/loadlvln.cpp b/components/esm4/loadlvln.cpp index febdcbeca9..3dff37614d 100644 --- a/components/esm4/loadlvln.cpp +++ b/components/esm4/loadlvln.cpp @@ -59,7 +59,7 @@ void ESM4::LevelledNpc::load(ESM4::Reader& reader) break; case ESM4::SUB_LVLO: { - static LVLO lvlo; + LVLO lvlo; if (subHdr.dataSize != 12) { if (subHdr.dataSize == 8) diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp index 885263d67b..7c91da747e 100644 --- a/components/esm4/loadnpc.cpp +++ b/components/esm4/loadnpc.cpp @@ -59,7 +59,7 @@ void ESM4::Npc::load(ESM4::Reader& reader) break; case ESM4::SUB_CNTO: { - static InventoryItem inv; // FIXME: use unique_ptr here? + InventoryItem inv; // FIXME: use unique_ptr here? reader.get(inv); reader.adjustFormId(inv.item); mInventory.push_back(inv); diff --git a/components/esm4/loadpack.cpp b/components/esm4/loadpack.cpp index ab75598121..34ef000934 100644 --- a/components/esm4/loadpack.cpp +++ b/components/esm4/loadpack.cpp @@ -103,7 +103,7 @@ void ESM4::AIPackage::load(ESM4::Reader& reader) break; } - static CTDA condition; + CTDA condition; reader.get(condition); // FIXME: how to "unadjust" if not FormId? // adjustFormId(condition.param1); diff --git a/components/esm4/loadpgrd.cpp b/components/esm4/loadpgrd.cpp index 12cbf6f28b..d88cf41e8a 100644 --- a/components/esm4/loadpgrd.cpp +++ b/components/esm4/loadpgrd.cpp @@ -68,7 +68,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) } case ESM4::SUB_PGRR: { - static PGRR link; + PGRR link; for (std::size_t i = 0; i < std::size_t(mData); ++i) // keep gcc quiet { @@ -105,7 +105,7 @@ void ESM4::Pathgrid::load(ESM4::Reader& reader) } case ESM4::SUB_PGRL: { - static PGRL objLink; + PGRL objLink; reader.getFormId(objLink.object); // object linkedNode std::size_t numNodes = (subHdr.dataSize - sizeof(int32_t)) / sizeof(int32_t); diff --git a/components/esm4/loadroad.cpp b/components/esm4/loadroad.cpp index 8a33ab1c1d..2cf5b2ed90 100644 --- a/components/esm4/loadroad.cpp +++ b/components/esm4/loadroad.cpp @@ -59,8 +59,8 @@ void ESM4::Road::load(ESM4::Reader& reader) } case ESM4::SUB_PGRR: { - static PGRR link; - static RDRP linkPt; + PGRR link; + RDRP linkPt; for (std::size_t i = 0; i < mNodes.size(); ++i) { diff --git a/components/esm4/loadscpt.cpp b/components/esm4/loadscpt.cpp index b4071ed21d..841bfbc839 100644 --- a/components/esm4/loadscpt.cpp +++ b/components/esm4/loadscpt.cpp @@ -36,7 +36,7 @@ void ESM4::Script::load(ESM4::Reader& reader) mId = reader.getFormIdFromHeader(); mFlags = reader.hdr().record.flags; - static ScriptLocalVariableData localVar; + ScriptLocalVariableData localVar; while (reader.getSubRecordHeader()) { From f49d270c26b7bec6a734046d65c98b271ab35038 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Mar 2024 00:54:12 +0000 Subject: [PATCH 281/451] Don't throw away user-provided shadow map resolutions Resolves https://gitlab.com/OpenMW/openmw/-/issues/7891 I think this is better than just adding 8192 as an allowed option as the vast majority of GPUs would be too slow given what we know about the cost if that setting (maybe that'll change if we get rid of the unconditional conditional discard I suspect is the cause of the slowness that's there for no good reason since the shadowsbin already moves most drawables to a known alpha-free stateset). --- apps/launcher/settingspage.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 93a724909e..1641fa07d7 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -246,6 +246,11 @@ bool Launcher::SettingsPage::loadSettings() int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes)); if (shadowResIndex != -1) shadowResolutionComboBox->setCurrentIndex(shadowResIndex); + else + { + shadowResolutionComboBox->addItem(QString::number(shadowRes)); + shadowResolutionComboBox->setCurrentIndex(shadowResolutionComboBox->count() - 1); + } connect(shadowDistanceCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotShadowDistLimitToggled); From b15f7857c07e788e93a2b78e2da09e12462b0832 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Mar 2024 14:34:23 +0000 Subject: [PATCH 282/451] currentDir.value is already canonicalised --- apps/launcher/datafilespage.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index d87073df02..87667bda37 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -293,10 +293,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) std::unordered_set visitedDirectories; for (const Config::SettingValue& currentDir : directories) { - // normalize user supplied directories: resolve symlink, convert to native separator - const QString canonicalDirPath = QDir(QDir::cleanPath(currentDir.value)).canonicalPath(); - - if (!visitedDirectories.insert(canonicalDirPath).second) + if (!visitedDirectories.insert(currentDir.value).second) continue; // add new achives files presents in current directory @@ -305,7 +302,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) QStringList tooltip; // add content files presents in current directory - mSelector->addFiles(currentDir.value, mNewDataDirs.contains(canonicalDirPath)); + mSelector->addFiles(currentDir.value, mNewDataDirs.contains(currentDir.value)); // add current directory to list ui.directoryListWidget->addItem(currentDir.originalRepresentation); @@ -317,7 +314,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) tooltip << tr("Resolved as %1").arg(currentDir.value); // Display new content with custom formatting - if (mNewDataDirs.contains(canonicalDirPath)) + if (mNewDataDirs.contains(currentDir.value)) { tooltip << tr("Will be added to the current profile"); QFont font = item->font(); From 0371791cce91a6bbc7ec8a8da94fb319cffc6728 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 20 Mar 2024 23:12:19 +0000 Subject: [PATCH 283/451] Break --- apps/launcher/maindialog.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index aca8a64e31..178b254545 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -373,7 +373,10 @@ bool Launcher::MainDialog::setupGameData() << "*.omwaddon"; if (!dir.entryList(filters).isEmpty()) + { foundData = true; + break; + } } if (!foundData) From 37b695a0cfc50a23f975d74d57e411239879be82 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 14:12:02 +0100 Subject: [PATCH 284/451] Cleanup includes --- apps/openmw/mwlua/animationbindings.cpp | 5 ++--- apps/openmw/mwlua/animationbindings.hpp | 2 ++ apps/openmw/mwlua/birthsignbindings.cpp | 6 ++---- apps/openmw/mwlua/camerabindings.cpp | 1 + apps/openmw/mwlua/classbindings.cpp | 9 ++------- apps/openmw/mwlua/debugbindings.cpp | 1 + apps/openmw/mwlua/factionbindings.cpp | 7 +------ apps/openmw/mwlua/inputbindings.cpp | 1 + apps/openmw/mwlua/itemdata.cpp | 4 +--- apps/openmw/mwlua/objectlists.cpp | 1 - apps/openmw/mwlua/racebindings.cpp | 5 ++--- apps/openmw/mwlua/vfsbindings.cpp | 4 +++- 12 files changed, 18 insertions(+), 28 deletions(-) diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index 5c4ccf7212..fb3e64ba73 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -1,3 +1,5 @@ +#include "animationbindings.hpp" + #include #include #include @@ -18,9 +20,6 @@ #include "luamanagerimp.hpp" #include "objectvariant.hpp" -#include "animationbindings.hpp" -#include - namespace MWLua { using BlendMask = MWRender::Animation::BlendMask; diff --git a/apps/openmw/mwlua/animationbindings.hpp b/apps/openmw/mwlua/animationbindings.hpp index d28dda9208..251de42ee8 100644 --- a/apps/openmw/mwlua/animationbindings.hpp +++ b/apps/openmw/mwlua/animationbindings.hpp @@ -5,6 +5,8 @@ namespace MWLua { + struct Context; + sol::table initAnimationPackage(const Context& context); sol::table initCoreVfxBindings(const Context& context); } diff --git a/apps/openmw/mwlua/birthsignbindings.cpp b/apps/openmw/mwlua/birthsignbindings.cpp index 6993ae0105..f65d50bc5a 100644 --- a/apps/openmw/mwlua/birthsignbindings.cpp +++ b/apps/openmw/mwlua/birthsignbindings.cpp @@ -1,15 +1,13 @@ +#include "birthsignbindings.hpp" + #include #include #include #include #include "../mwbase/environment.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" -#include "birthsignbindings.hpp" #include "idcollectionbindings.hpp" -#include "luamanagerimp.hpp" #include "types/types.hpp" namespace sol diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index e3470eb853..ed75b4b198 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -1,3 +1,4 @@ +#include "camerabindings.hpp" #include #include diff --git a/apps/openmw/mwlua/classbindings.cpp b/apps/openmw/mwlua/classbindings.cpp index c9d5a9fb7b..84864781d2 100644 --- a/apps/openmw/mwlua/classbindings.cpp +++ b/apps/openmw/mwlua/classbindings.cpp @@ -1,14 +1,9 @@ +#include "classbindings.hpp" + #include #include -#include "../mwbase/environment.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" - -#include "classbindings.hpp" #include "idcollectionbindings.hpp" -#include "luamanagerimp.hpp" -#include "stats.hpp" #include "types/types.hpp" namespace sol diff --git a/apps/openmw/mwlua/debugbindings.cpp b/apps/openmw/mwlua/debugbindings.cpp index 0aa1f4ace5..97ca080e5c 100644 --- a/apps/openmw/mwlua/debugbindings.cpp +++ b/apps/openmw/mwlua/debugbindings.cpp @@ -1,4 +1,5 @@ #include "debugbindings.hpp" + #include "context.hpp" #include "luamanagerimp.hpp" diff --git a/apps/openmw/mwlua/factionbindings.cpp b/apps/openmw/mwlua/factionbindings.cpp index e4c65386bf..b606d1a6f9 100644 --- a/apps/openmw/mwlua/factionbindings.cpp +++ b/apps/openmw/mwlua/factionbindings.cpp @@ -3,14 +3,9 @@ #include #include -#include "../mwbase/environment.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" - -#include "../mwmechanics/npcstats.hpp" +#include "../mwworld/store.hpp" #include "idcollectionbindings.hpp" -#include "luamanagerimp.hpp" namespace { diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index ae54061cb6..e9ed4fe485 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -11,6 +11,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwinput/actions.hpp" + #include "luamanagerimp.hpp" namespace sol diff --git a/apps/openmw/mwlua/itemdata.cpp b/apps/openmw/mwlua/itemdata.cpp index d7ced755ea..3e2b755af8 100644 --- a/apps/openmw/mwlua/itemdata.cpp +++ b/apps/openmw/mwlua/itemdata.cpp @@ -1,13 +1,11 @@ #include "itemdata.hpp" #include "context.hpp" - #include "luamanagerimp.hpp" +#include "objectvariant.hpp" #include "../mwworld/class.hpp" -#include "objectvariant.hpp" - namespace { using SelfObject = MWLua::SelfObject; diff --git a/apps/openmw/mwlua/objectlists.cpp b/apps/openmw/mwlua/objectlists.cpp index e7b3bb2e06..d0bda5a644 100644 --- a/apps/openmw/mwlua/objectlists.cpp +++ b/apps/openmw/mwlua/objectlists.cpp @@ -7,7 +7,6 @@ #include #include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" #include "../mwclass/container.hpp" diff --git a/apps/openmw/mwlua/racebindings.cpp b/apps/openmw/mwlua/racebindings.cpp index e2e2ae2a8a..ea23e883e1 100644 --- a/apps/openmw/mwlua/racebindings.cpp +++ b/apps/openmw/mwlua/racebindings.cpp @@ -1,14 +1,13 @@ +#include "racebindings.hpp" + #include #include #include #include "../mwbase/environment.hpp" -#include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "idcollectionbindings.hpp" -#include "luamanagerimp.hpp" -#include "racebindings.hpp" #include "types/types.hpp" namespace diff --git a/apps/openmw/mwlua/vfsbindings.cpp b/apps/openmw/mwlua/vfsbindings.cpp index 34a84221f8..0e13c07ef9 100644 --- a/apps/openmw/mwlua/vfsbindings.cpp +++ b/apps/openmw/mwlua/vfsbindings.cpp @@ -1,6 +1,9 @@ #include "vfsbindings.hpp" +#include + #include +#include #include #include #include @@ -10,7 +13,6 @@ #include "../mwbase/environment.hpp" #include "context.hpp" -#include "luamanagerimp.hpp" namespace MWLua { From 3721a69747b388fa9d144e4867cae33241a6ca36 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 21 Mar 2024 18:16:11 +0300 Subject: [PATCH 285/451] ESM4: Make script local variable loading more reliable --- components/esm4/loadinfo.cpp | 11 ++++++----- components/esm4/loadscpt.cpp | 12 +++++++----- components/esm4/script.hpp | 7 ------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/components/esm4/loadinfo.cpp b/components/esm4/loadinfo.cpp index 40e366d5d7..d3339d350a 100644 --- a/components/esm4/loadinfo.cpp +++ b/components/esm4/loadinfo.cpp @@ -41,7 +41,6 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) mEditorId = ESM::RefId(mId).serializeText(); // FIXME: quick workaround to use existing code - ScriptLocalVariableData localVar; bool ignore = false; while (reader.getSubRecordHeader()) @@ -125,22 +124,24 @@ void ESM4::DialogInfo::load(ESM4::Reader& reader) break; case ESM::fourCC("SLSD"): { - localVar.clear(); + ScriptLocalVariableData localVar; reader.get(localVar.index); reader.get(localVar.unknown1); reader.get(localVar.unknown2); reader.get(localVar.unknown3); reader.get(localVar.type); reader.get(localVar.unknown4); + mScript.localVarData.push_back(std::move(localVar)); // WARN: assumes SCVR will follow immediately break; } case ESM::fourCC("SCVR"): // assumed always pair with SLSD { - reader.getZString(localVar.variableName); - - mScript.localVarData.push_back(localVar); + if (!mScript.localVarData.empty()) + reader.getZString(mScript.localVarData.back().variableName); + else + reader.skipSubRecordData(); break; } diff --git a/components/esm4/loadscpt.cpp b/components/esm4/loadscpt.cpp index 7eaef816c8..dbc75b75d6 100644 --- a/components/esm4/loadscpt.cpp +++ b/components/esm4/loadscpt.cpp @@ -36,8 +36,6 @@ void ESM4::Script::load(ESM4::Reader& reader) mId = reader.getFormIdFromHeader(); mFlags = reader.hdr().record.flags; - ScriptLocalVariableData localVar; - while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); @@ -117,20 +115,24 @@ void ESM4::Script::load(ESM4::Reader& reader) break; case ESM::fourCC("SLSD"): { - localVar.clear(); + ScriptLocalVariableData localVar; reader.get(localVar.index); reader.get(localVar.unknown1); reader.get(localVar.unknown2); reader.get(localVar.unknown3); reader.get(localVar.type); reader.get(localVar.unknown4); + mScript.localVarData.push_back(std::move(localVar)); // WARN: assumes SCVR will follow immediately break; } case ESM::fourCC("SCVR"): // assumed always pair with SLSD - reader.getZString(localVar.variableName); - mScript.localVarData.push_back(localVar); + if (!mScript.localVarData.empty()) + reader.getZString(mScript.localVarData.back().variableName); + else + reader.skipSubRecordData(); + break; case ESM::fourCC("SCRV"): { diff --git a/components/esm4/script.hpp b/components/esm4/script.hpp index 57dd85367b..3715ea55d4 100644 --- a/components/esm4/script.hpp +++ b/components/esm4/script.hpp @@ -365,13 +365,6 @@ namespace ESM4 std::uint32_t unknown4; // SCVR std::string variableName; - - void clear() - { - index = 0; - type = 0; - variableName.clear(); - } }; struct ScriptDefinition From da8150e2e4dcfca3e7fed213b402ed3fa7a6ae3a Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 21 Mar 2024 15:51:29 +0000 Subject: [PATCH 286/451] Even more MSVC-specific warnings that evaded detection in CI --- apps/openmw/mwstate/character.cpp | 8 ++++---- components/esm3/loadland.cpp | 4 ++-- components/esm3/loadlevlist.cpp | 2 +- components/esm3/loadscpt.cpp | 2 +- components/esm4/reader.cpp | 8 ++++---- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index 9a3bc46742..a486ff4bec 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -132,9 +132,9 @@ const MWState::Slot* MWState::Character::createSlot(const ESM::SavedGame& profil void MWState::Character::deleteSlot(const Slot* slot) { - int index = slot - mSlots.data(); + std::ptrdiff_t index = slot - mSlots.data(); - if (index < 0 || index >= static_cast(mSlots.size())) + if (index < 0 || static_cast(index) >= mSlots.size()) { // sanity check; not entirely reliable throw std::logic_error("slot not found"); @@ -147,9 +147,9 @@ void MWState::Character::deleteSlot(const Slot* slot) const MWState::Slot* MWState::Character::updateSlot(const Slot* slot, const ESM::SavedGame& profile) { - int index = slot - mSlots.data(); + std::ptrdiff_t index = slot - mSlots.data(); - if (index < 0 || index >= static_cast(mSlots.size())) + if (index < 0 || static_cast(index) >= mSlots.size()) { // sanity check; not entirely reliable throw std::logic_error("slot not found"); diff --git a/components/esm3/loadland.cpp b/components/esm3/loadland.cpp index 98e07b530f..006510f21b 100644 --- a/components/esm3/loadland.cpp +++ b/components/esm3/loadland.cpp @@ -94,7 +94,7 @@ namespace ESM mDataTypes |= DATA_VHGT; break; case fourCC("WNAM"): - esm.getHExact(mWnam.data(), mWnam.size()); + esm.getHExact(mWnam.data(), static_cast(mWnam.size())); mDataTypes |= DATA_WNAM; break; case fourCC("VCLR"): @@ -206,7 +206,7 @@ namespace ESM mLandData = std::make_unique(); mLandData->mHeightOffset = 0; - std::fill(std::begin(mLandData->mHeights), std::end(mLandData->mHeights), 0); + std::fill(std::begin(mLandData->mHeights), std::end(mLandData->mHeights), 0.0f); mLandData->mMinHeight = 0; mLandData->mMaxHeight = 0; for (int i = 0; i < LAND_NUM_VERTS; ++i) diff --git a/components/esm3/loadlevlist.cpp b/components/esm3/loadlevlist.cpp index 627edbadce..766fd42054 100644 --- a/components/esm3/loadlevlist.cpp +++ b/components/esm3/loadlevlist.cpp @@ -87,7 +87,7 @@ namespace ESM esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); - esm.writeHNT("INDX", mList.size()); + esm.writeHNT("INDX", static_cast(mList.size())); for (const auto& item : mList) { diff --git a/components/esm3/loadscpt.cpp b/components/esm3/loadscpt.cpp index 2eb272fe8b..ae56a7b4f4 100644 --- a/components/esm3/loadscpt.cpp +++ b/components/esm3/loadscpt.cpp @@ -150,7 +150,7 @@ namespace ESM if (!hasHeader) esm.fail("Missing SCHD subrecord"); // Reported script data size is not always trustworthy, so override it with actual data size - mData.mScriptDataSize = mScriptData.size(); + mData.mScriptDataSize = static_cast(mScriptData.size()); } void Script::save(ESMWriter& esm, bool isDeleted) const diff --git a/components/esm4/reader.cpp b/components/esm4/reader.cpp index 9811cf6103..2d9a929bb2 100644 --- a/components/esm4/reader.cpp +++ b/components/esm4/reader.cpp @@ -83,8 +83,8 @@ namespace ESM4 stream.next_in = reinterpret_cast(compressed.data()); stream.next_out = reinterpret_cast(decompressed.data()); - stream.avail_in = compressed.size(); - stream.avail_out = decompressed.size(); + stream.avail_in = static_cast(compressed.size()); + stream.avail_out = static_cast(decompressed.size()); if (const int ec = inflateInit(&stream); ec != Z_OK) return getError("inflateInit error", ec, stream.msg); @@ -112,9 +112,9 @@ namespace ESM4 const auto prevTotalIn = stream.total_in; const auto prevTotalOut = stream.total_out; stream.next_in = reinterpret_cast(compressed.data()); - stream.avail_in = std::min(blockSize, compressed.size()); + stream.avail_in = static_cast(std::min(blockSize, compressed.size())); stream.next_out = reinterpret_cast(decompressed.data()); - stream.avail_out = std::min(blockSize, decompressed.size()); + stream.avail_out = static_cast(std::min(blockSize, decompressed.size())); const int ec = inflate(&stream, Z_NO_FLUSH); if (ec == Z_STREAM_END) break; From 818a99a8707fc9b69530fc4809d654f5edce6b2e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 21 Mar 2024 16:18:18 +0000 Subject: [PATCH 287/451] Review --- components/esm3/esmreader.cpp | 10 +++++----- components/esm3/esmreader.hpp | 6 +++--- components/esm3/loadland.cpp | 4 ++-- components/esm3/loadlevlist.cpp | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index 92a04fb487..c52e739f5f 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -244,16 +244,16 @@ namespace ESM skipHString(); } - void ESMReader::getHExact(void* p, int size) + void ESMReader::getHExact(void* p, std::size_t size) { getSubHeader(); - if (size != static_cast(mCtx.leftSub)) + if (size != mCtx.leftSub) reportSubSizeMismatch(size, mCtx.leftSub); getExact(p, size); } // Read the given number of bytes from a named subrecord - void ESMReader::getHNExact(void* p, int size, NAME name) + void ESMReader::getHNExact(void* p, std::size_t size, NAME name) { getSubNameIs(name); getHExact(p, size); @@ -326,10 +326,10 @@ namespace ESM skip(mCtx.leftSub); } - void ESMReader::skipHSubSize(int size) + void ESMReader::skipHSubSize(std::size_t size) { skipHSub(); - if (static_cast(mCtx.leftSub) != size) + if (mCtx.leftSub != size) reportSubSizeMismatch(mCtx.leftSub, size); } diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 7d0b9b980c..b67cc0f8bb 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -238,10 +238,10 @@ namespace ESM void skipHRefId(); // Read the given number of bytes from a subrecord - void getHExact(void* p, int size); + void getHExact(void* p, std::size_t size); // Read the given number of bytes from a named subrecord - void getHNExact(void* p, int size, NAME name); + void getHNExact(void* p, std::size_t size, NAME name); ESM::FormId getFormId(bool wide = false, NAME tag = "FRMR"); @@ -275,7 +275,7 @@ namespace ESM void skipHSub(); // Skip sub record and check its size - void skipHSubSize(int size); + void skipHSubSize(std::size_t size); // Skip all subrecords until the given subrecord or no more subrecords remaining void skipHSubUntil(NAME name); diff --git a/components/esm3/loadland.cpp b/components/esm3/loadland.cpp index 006510f21b..8b8a8f90fa 100644 --- a/components/esm3/loadland.cpp +++ b/components/esm3/loadland.cpp @@ -25,7 +25,7 @@ namespace ESM // Loads data and marks it as loaded. Return true if data is actually loaded from reader, false otherwise // including the case when data is already loaded. - bool condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, void* ptr, unsigned int size) + bool condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, void* ptr, std::size_t size) { if ((targetFlags & dataFlag) == 0 && (flags & dataFlag) != 0) { @@ -94,7 +94,7 @@ namespace ESM mDataTypes |= DATA_VHGT; break; case fourCC("WNAM"): - esm.getHExact(mWnam.data(), static_cast(mWnam.size())); + esm.getHExact(mWnam.data(), mWnam.size()); mDataTypes |= DATA_WNAM; break; case fourCC("VCLR"): diff --git a/components/esm3/loadlevlist.cpp b/components/esm3/loadlevlist.cpp index 766fd42054..f37009d6f9 100644 --- a/components/esm3/loadlevlist.cpp +++ b/components/esm3/loadlevlist.cpp @@ -87,7 +87,7 @@ namespace ESM esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); - esm.writeHNT("INDX", static_cast(mList.size())); + esm.writeHNT("INDX", static_cast(mList.size())); for (const auto& item : mList) { From 79039f88df4f0390cd62bd76c3211dda2760197a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 22 Mar 2024 04:25:32 +0300 Subject: [PATCH 288/451] Use the right ID for magic effect verifier messages (#7894) --- apps/opencs/model/tools/magiceffectcheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/tools/magiceffectcheck.cpp b/apps/opencs/model/tools/magiceffectcheck.cpp index a9ad4023fc..e44119bb67 100644 --- a/apps/opencs/model/tools/magiceffectcheck.cpp +++ b/apps/opencs/model/tools/magiceffectcheck.cpp @@ -58,7 +58,7 @@ void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages& messa return; ESM::MagicEffect effect = record.get(); - CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, effect.mId); + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, CSMWorld::getRecordId(effect)); if (effect.mDescription.empty()) { From c20a23b694bf2d294e74ddd972661c6025654f22 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 22 Mar 2024 03:13:04 +0000 Subject: [PATCH 289/451] Remove unused regionmap CellDescription constructor --- apps/opencs/model/world/regionmap.cpp | 5 ----- apps/opencs/model/world/regionmap.hpp | 2 -- 2 files changed, 7 deletions(-) diff --git a/apps/opencs/model/world/regionmap.cpp b/apps/opencs/model/world/regionmap.cpp index f555f0ea32..79a0d5474d 100644 --- a/apps/opencs/model/world/regionmap.cpp +++ b/apps/opencs/model/world/regionmap.cpp @@ -41,11 +41,6 @@ namespace CSMWorld } } -CSMWorld::RegionMap::CellDescription::CellDescription() - : mDeleted(false) -{ -} - CSMWorld::RegionMap::CellDescription::CellDescription(const Record& cell, float landHeight) { const Cell& cell2 = cell.get(); diff --git a/apps/opencs/model/world/regionmap.hpp b/apps/opencs/model/world/regionmap.hpp index e5a4d61337..96281ba49c 100644 --- a/apps/opencs/model/world/regionmap.hpp +++ b/apps/opencs/model/world/regionmap.hpp @@ -45,8 +45,6 @@ namespace CSMWorld ESM::RefId mRegion; std::string mName; - CellDescription(); - CellDescription(const Record& cell, float landHeight); }; From d6241dd1c5956c28068c66bfbe5218aa820321fc Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 22 Mar 2024 17:41:12 -0500 Subject: [PATCH 290/451] Add back new_index --- apps/openmw/mwlua/mwscriptbindings.cpp | 37 ++++++++++++++++++++------ 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index 6ccb8c80fd..1f0a081fe4 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -67,6 +67,19 @@ namespace MWLua return 0; } + void setGlobalVariableValue(const std::string_view globalId, float value) + { + char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); + if (varType == 'f') + { + MWBase::Environment::get().getWorld()->setGlobalFloat(globalId, value); + } + else if (varType == 's' || varType == 'l') + { + MWBase::Environment::get().getWorld()->setGlobalInt(globalId, value); + } + } + sol::table initMWScriptBindings(const Context& context) { sol::table api(context.mLua->sol(), sol::create); @@ -155,14 +168,22 @@ namespace MWLua std::string globalId = g->mId.serializeText(); return getGlobalVariableValue(globalId); }); - - globalStoreT[sol::meta_function::new_index] - = sol::overload([](const GlobalStore& store, std::string_view globalId, float val) { - auto g = store.search(ESM::RefId::deserializeText(globalId)); - if (g == nullptr) - throw std::runtime_error("No variable \"" + std::string(globalId) + "\" in GlobalStore"); - return getGlobalVariableValue(globalId); - }); + globalStoreT[sol::meta_function::new_index] = sol::overload( + [](const GlobalStore& store, std::string_view globalId, float val) -> void { + auto g = store.search(ESM::RefId::deserializeText(globalId)); + if (g == nullptr) + throw std::runtime_error("No variable \"" + std::string(globalId) + "\" in GlobalStore"); + setGlobalVariableValue(globalId, val); + }, + [](const GlobalStore& store, size_t index, float val) { + if (index < 1 || store.getSize() < index) + return; + auto g = store.at(index - 1); + if (g == nullptr) + return; + std::string globalId = g->mId.serializeText(); + setGlobalVariableValue(globalId, val); + }); globalStoreT[sol::meta_function::pairs] = [](const GlobalStore& store) { size_t index = 0; return sol::as_function( From 4634c7dba95e9b0cfe2d9db38d09fb337710c53d Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 22 Mar 2024 18:56:15 -0500 Subject: [PATCH 291/451] Add iteration global tests --- example-suite | 1 + .../integration_tests/test_lua_api/test.lua | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 160000 example-suite diff --git a/example-suite b/example-suite new file mode 160000 index 0000000000..f0c62b7e46 --- /dev/null +++ b/example-suite @@ -0,0 +1 @@ +Subproject commit f0c62b7e4637badb324e782c97169560e8171032 diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index 2ec9f09b97..775f3ba499 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -2,6 +2,7 @@ local testing = require('testing_util') local core = require('openmw.core') local async = require('openmw.async') local util = require('openmw.util') +local world = require('openmw.world') local function testTimers() testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result') @@ -64,6 +65,28 @@ local function testGetGMST() testing.expectEqual(core.getGMST('Level_Up_Level2'), 'something') end +local function testMWScript() + local variableStoreCount = 18 + local variableStore = world.mwscript.getGlobalVariables(player) + testing.expectEqual(variableStoreCount,#variableStore) + + variableStore.year = variableStoreCount + testing.expectEqual(variableStoreCount,variableStore.year) + variableStore.year = 1 + local indexCheck = 0 + for index, value in ipairs(variableStore) do + testing.expectEqual(variableStore[index],value) + indexCheck = indexCheck + 1 + end + testing.expectEqual(variableStoreCount,indexCheck) + indexCheck = 0 + for index, value in pairs(variableStore) do + testing.expectEqual(variableStore[index],value) + indexCheck = indexCheck + 1 + end + testing.expectEqual(variableStoreCount,indexCheck) +end + local function initPlayer() player:teleport('', util.vector3(4096, 4096, 867.237), util.transform.identity) coroutine.yield() @@ -101,6 +124,7 @@ tests = { end}, {'teleport', testTeleport}, {'getGMST', testGetGMST}, + {'mwscript', testMWScript}, } return { From b8c8e304319c5ea9e853f5b48b3d604cbd7aea32 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 22 Mar 2024 19:06:00 -0500 Subject: [PATCH 292/451] Revert "Add iteration global tests" This reverts commit 4634c7dba95e9b0cfe2d9db38d09fb337710c53d. --- example-suite | 1 - .../integration_tests/test_lua_api/test.lua | 24 ------------------- 2 files changed, 25 deletions(-) delete mode 160000 example-suite diff --git a/example-suite b/example-suite deleted file mode 160000 index f0c62b7e46..0000000000 --- a/example-suite +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f0c62b7e4637badb324e782c97169560e8171032 diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index 775f3ba499..2ec9f09b97 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -2,7 +2,6 @@ local testing = require('testing_util') local core = require('openmw.core') local async = require('openmw.async') local util = require('openmw.util') -local world = require('openmw.world') local function testTimers() testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result') @@ -65,28 +64,6 @@ local function testGetGMST() testing.expectEqual(core.getGMST('Level_Up_Level2'), 'something') end -local function testMWScript() - local variableStoreCount = 18 - local variableStore = world.mwscript.getGlobalVariables(player) - testing.expectEqual(variableStoreCount,#variableStore) - - variableStore.year = variableStoreCount - testing.expectEqual(variableStoreCount,variableStore.year) - variableStore.year = 1 - local indexCheck = 0 - for index, value in ipairs(variableStore) do - testing.expectEqual(variableStore[index],value) - indexCheck = indexCheck + 1 - end - testing.expectEqual(variableStoreCount,indexCheck) - indexCheck = 0 - for index, value in pairs(variableStore) do - testing.expectEqual(variableStore[index],value) - indexCheck = indexCheck + 1 - end - testing.expectEqual(variableStoreCount,indexCheck) -end - local function initPlayer() player:teleport('', util.vector3(4096, 4096, 867.237), util.transform.identity) coroutine.yield() @@ -124,7 +101,6 @@ tests = { end}, {'teleport', testTeleport}, {'getGMST', testGetGMST}, - {'mwscript', testMWScript}, } return { From b51891cbcdd6ed639b58ea8cf65d3f30e2deca68 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 22 Mar 2024 19:13:39 -0500 Subject: [PATCH 293/451] Add lua global var test back --- .../integration_tests/test_lua_api/test.lua | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index 2ec9f09b97..0eead01ff0 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -2,6 +2,7 @@ local testing = require('testing_util') local core = require('openmw.core') local async = require('openmw.async') local util = require('openmw.util') +local world = require('openmw.world') local function testTimers() testing.expectAlmostEqual(core.getGameTimeScale(), 30, 'incorrect getGameTimeScale() result') @@ -64,6 +65,28 @@ local function testGetGMST() testing.expectEqual(core.getGMST('Level_Up_Level2'), 'something') end +local function testMWScript() + local variableStoreCount = 18 + local variableStore = world.mwscript.getGlobalVariables(player) + testing.expectEqual(variableStoreCount,#variableStore) + + variableStore.year = variableStoreCount + testing.expectEqual(variableStoreCount,variableStore.year) + variableStore.year = 1 + local indexCheck = 0 + for index, value in ipairs(variableStore) do + testing.expectEqual(variableStore[index],value) + indexCheck = indexCheck + 1 + end + testing.expectEqual(variableStoreCount,indexCheck) + indexCheck = 0 + for index, value in pairs(variableStore) do + testing.expectEqual(variableStore[index],value) + indexCheck = indexCheck + 1 + end + testing.expectEqual(variableStoreCount,indexCheck) +end + local function initPlayer() player:teleport('', util.vector3(4096, 4096, 867.237), util.transform.identity) coroutine.yield() @@ -101,6 +124,7 @@ tests = { end}, {'teleport', testTeleport}, {'getGMST', testGetGMST}, + {'mwscript', testMWScript}, } return { @@ -109,4 +133,4 @@ return { onPlayerAdded = function(p) player = p end, }, eventHandlers = testing.eventHandlers, -} +} \ No newline at end of file From 7d1f52451f953be5842c35e90b2d9741a5c69ed7 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 22 Mar 2024 19:14:28 -0500 Subject: [PATCH 294/451] Re-add new line --- scripts/data/integration_tests/test_lua_api/test.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index 0eead01ff0..775f3ba499 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -133,4 +133,4 @@ return { onPlayerAdded = function(p) player = p end, }, eventHandlers = testing.eventHandlers, -} \ No newline at end of file +} From 1aff88e6a3fc5637f65c840686898a0008e77284 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 23 Mar 2024 00:33:50 +0000 Subject: [PATCH 295/451] Even more warning fixes --- apps/openmw/mwdialogue/keywordsearch.hpp | 2 +- apps/openmw_test_suite/openmw/options.cpp | 2 +- apps/openmw_test_suite/sqlite3/request.cpp | 4 ++-- apps/openmw_test_suite/toutf8/toutf8.cpp | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 3b784cd59c..c93a52e43e 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -87,7 +87,7 @@ namespace MWDialogue // some keywords might be longer variations of other keywords, so we definitely need a list of // candidates the first element in the pair is length of the match, i.e. depth from the first character // on - std::vector> candidates; + std::vector> candidates; while ((j + 1) != end) { diff --git a/apps/openmw_test_suite/openmw/options.cpp b/apps/openmw_test_suite/openmw/options.cpp index fc89264f8c..fe319f64fa 100644 --- a/apps/openmw_test_suite/openmw/options.cpp +++ b/apps/openmw_test_suite/openmw/options.cpp @@ -36,7 +36,7 @@ namespace (result.emplace_back(makeString(args)), ...); for (int i = 1; i <= std::numeric_limits::max(); ++i) if (i != '&' && i != '"' && i != ' ' && i != '\n') - result.push_back(std::string(1, i)); + result.push_back(std::string(1, static_cast(i))); return result; } diff --git a/apps/openmw_test_suite/sqlite3/request.cpp b/apps/openmw_test_suite/sqlite3/request.cpp index 23efe9dc2e..c299493952 100644 --- a/apps/openmw_test_suite/sqlite3/request.cpp +++ b/apps/openmw_test_suite/sqlite3/request.cpp @@ -151,7 +151,7 @@ namespace const std::int64_t value = 1099511627776; EXPECT_EQ(execute(*mDb, insert, value), 1); Statement select(*mDb, GetAll("ints")); - std::vector> result; + std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max()); EXPECT_THAT(result, ElementsAre(std::tuple(value))); } @@ -205,7 +205,7 @@ namespace const std::int64_t value = 1099511627776; EXPECT_EQ(execute(*mDb, insert, value), 1); Statement select(*mDb, GetExact("ints")); - std::vector> result; + std::vector> result; request(*mDb, select, std::back_inserter(result), std::numeric_limits::max(), value); EXPECT_THAT(result, ElementsAre(std::tuple(value))); } diff --git a/apps/openmw_test_suite/toutf8/toutf8.cpp b/apps/openmw_test_suite/toutf8/toutf8.cpp index f189294cf2..9a259c69ab 100644 --- a/apps/openmw_test_suite/toutf8/toutf8.cpp +++ b/apps/openmw_test_suite/toutf8/toutf8.cpp @@ -47,7 +47,7 @@ namespace { std::string input; for (int c = 1; c <= std::numeric_limits::max(); ++c) - input.push_back(c); + input.push_back(static_cast(c)); Utf8Encoder encoder(FromType::CP437); const std::string_view result = encoder.getUtf8(input); EXPECT_EQ(result.data(), input.data()); @@ -99,7 +99,7 @@ namespace { std::string input; for (int c = 1; c <= std::numeric_limits::max(); ++c) - input.push_back(c); + input.push_back(static_cast(c)); Utf8Encoder encoder(FromType::CP437); const std::string_view result = encoder.getLegacyEnc(input); EXPECT_EQ(result.data(), input.data()); From 7c857559503acc06fc403a325a731330a9447776 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 23 Mar 2024 02:34:57 +0000 Subject: [PATCH 296/451] Warning that doesn't fire with MSVC 2022 Hopefully this fixes it. I've only tried MSVC 2022 locally, so can't verify this fix. --- apps/openmw/mwdialogue/keywordsearch.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index c93a52e43e..2c98eac218 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -148,11 +148,11 @@ namespace MWDialogue // resolve overlapping keywords while (!matches.empty()) { - int longestKeywordSize = 0; + std::size_t longestKeywordSize = 0; typename std::vector::iterator longestKeyword = matches.begin(); for (typename std::vector::iterator it = matches.begin(); it != matches.end(); ++it) { - int size = it->mEnd - it->mBeg; + std::size_t size = it->mEnd - it->mBeg; if (size > longestKeywordSize) { longestKeywordSize = size; @@ -199,7 +199,7 @@ namespace MWDialogue void seed_impl(std::string_view keyword, value_t value, size_t depth, Entry& entry) { - int ch = Misc::StringUtils::toLower(keyword.at(depth)); + auto ch = Misc::StringUtils::toLower(keyword.at(depth)); typename Entry::childen_t::iterator j = entry.mChildren.find(ch); From 5a0aed3a78edeca440dd2b3c50120bb0bb3a3f19 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 23 Mar 2024 12:15:09 +0100 Subject: [PATCH 297/451] Use more decomposition, string_view, and implicit sizes in ESM code --- apps/esmtool/record.cpp | 3 - apps/essimporter/converter.cpp | 2 +- apps/essimporter/importcellref.cpp | 34 ++++++--- apps/essimporter/importer.cpp | 2 +- components/esm/luascripts.cpp | 2 +- components/esm3/esmreader.cpp | 29 ++------ components/esm3/esmreader.hpp | 8 +-- components/esm3/esmwriter.cpp | 20 +++--- components/esm3/esmwriter.hpp | 21 +++--- components/esm3/landrecorddata.hpp | 13 ++-- components/esm3/loadland.cpp | 108 ++++++++++++++++------------- components/esm3/loadland.hpp | 24 ++----- 12 files changed, 124 insertions(+), 142 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 912ad0d683..b83c711476 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -896,9 +896,6 @@ namespace EsmTool if (const ESM::Land::LandData* data = mData.getLandData(mData.mDataTypes)) { std::cout << " Height Offset: " << data->mHeightOffset << std::endl; - // Lots of missing members. - std::cout << " Unknown1: " << data->mUnk1 << std::endl; - std::cout << " Unknown2: " << static_cast(data->mUnk2) << std::endl; } mData.unloadData(); std::cout << " Deleted: " << mIsDeleted << std::endl; diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 4c4bd1e438..ebb0c9d281 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -232,7 +232,7 @@ namespace ESSImport esm.skip(4); } - esm.getExact(nam8, 32); + esm.getT(nam8); newcell.mFogOfWar.reserve(16 * 16); for (int x = 0; x < 16; ++x) diff --git a/apps/essimporter/importcellref.cpp b/apps/essimporter/importcellref.cpp index 56e888d3f6..9e8e9a6948 100644 --- a/apps/essimporter/importcellref.cpp +++ b/apps/essimporter/importcellref.cpp @@ -1,10 +1,30 @@ #include "importcellref.hpp" #include +#include + #include namespace ESSImport { + template T> + void decompose(T&& v, const auto& f) + { + f(v.mUnknown, v.mFlags, v.mBreathMeter, v.mUnknown2, v.mDynamic, v.mUnknown3, v.mAttributes, v.mMagicEffects, + v.mUnknown4, v.mGoldPool, v.mCountDown, v.mUnknown5); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mUnknown1, v.mFlags, v.mUnknown2, v.mCorpseClearCountdown, v.mUnknown3); + } + + template T> + void decompose(T&& v, const auto& f) + { + f(v.mGroupIndex, v.mUnknown, v.mTime); + } void CellRef::load(ESM::ESMReader& esm) { @@ -45,14 +65,9 @@ namespace ESSImport bool isDeleted = false; ESM::CellRef::loadData(esm, isDeleted); - mActorData.mHasACDT - = esm.getHNOT("ACDT", mActorData.mACDT.mUnknown, mActorData.mACDT.mFlags, mActorData.mACDT.mBreathMeter, - mActorData.mACDT.mUnknown2, mActorData.mACDT.mDynamic, mActorData.mACDT.mUnknown3, - mActorData.mACDT.mAttributes, mActorData.mACDT.mMagicEffects, mActorData.mACDT.mUnknown4, - mActorData.mACDT.mGoldPool, mActorData.mACDT.mCountDown, mActorData.mACDT.mUnknown5); + mActorData.mHasACDT = esm.getOptionalComposite("ACDT", mActorData.mACDT); - mActorData.mHasACSC = esm.getHNOT("ACSC", mActorData.mACSC.mUnknown1, mActorData.mACSC.mFlags, - mActorData.mACSC.mUnknown2, mActorData.mACSC.mCorpseClearCountdown, mActorData.mACSC.mUnknown3); + mActorData.mHasACSC = esm.getOptionalComposite("ACSC", mActorData.mACSC); if (esm.isNextSub("ACSL")) esm.skipHSubSize(112); @@ -127,8 +142,7 @@ namespace ESSImport if (esm.isNextSub("ND3D")) esm.skipHSub(); - mActorData.mHasANIS - = esm.getHNOT("ANIS", mActorData.mANIS.mGroupIndex, mActorData.mANIS.mUnknown, mActorData.mANIS.mTime); + mActorData.mHasANIS = esm.getOptionalComposite("ANIS", mActorData.mANIS); if (esm.isNextSub("LVCR")) { @@ -146,7 +160,7 @@ namespace ESSImport // I've seen DATA *twice* on a creature record, and with the exact same content too! weird // alarmvoi0000.ess for (int i = 0; i < 2; ++i) - esm.getHNOT("DATA", mPos.pos, mPos.rot); + esm.getOptionalComposite("DATA", mPos); mDeleted = 0; if (esm.isNextSub("DELE")) diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 76b685c8a3..5cc9a8259b 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -135,7 +135,7 @@ namespace ESSImport sub.mFileOffset = esm.getFileOffset(); sub.mName = esm.retSubName().toString(); sub.mData.resize(esm.getSubSize()); - esm.getExact(&sub.mData[0], sub.mData.size()); + esm.getExact(sub.mData.data(), sub.mData.size()); rec.mSubrecords.push_back(sub); } file.mRecords.push_back(rec); diff --git a/components/esm/luascripts.cpp b/components/esm/luascripts.cpp index 8f2048d8a7..71e2ce6dc1 100644 --- a/components/esm/luascripts.cpp +++ b/components/esm/luascripts.cpp @@ -38,7 +38,7 @@ std::string ESM::loadLuaBinaryData(ESMReader& esm) { esm.getSubHeader(); data.resize(esm.getSubSize()); - esm.getExact(data.data(), static_cast(data.size())); + esm.getExact(data.data(), data.size()); } return data; } diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index c52e739f5f..4f69b8edef 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -153,7 +153,7 @@ namespace ESM { if (isNextSub(name)) return getHString(); - return ""; + return {}; } ESM::RefId ESMReader::getHNORefId(NAME name) @@ -244,21 +244,6 @@ namespace ESM skipHString(); } - void ESMReader::getHExact(void* p, std::size_t size) - { - getSubHeader(); - if (size != mCtx.leftSub) - reportSubSizeMismatch(size, mCtx.leftSub); - getExact(p, size); - } - - // Read the given number of bytes from a named subrecord - void ESMReader::getHNExact(void* p, std::size_t size, NAME name) - { - getSubNameIs(name); - getHExact(p, size); - } - FormId ESMReader::getFormId(bool wide, NAME tag) { FormId res; @@ -316,7 +301,7 @@ namespace ESM // reading the subrecord data anyway. const std::size_t subNameSize = decltype(mCtx.subName)::sCapacity; - getExact(mCtx.subName.mData, static_cast(subNameSize)); + getExact(mCtx.subName.mData, subNameSize); mCtx.leftRec -= static_cast(subNameSize); } @@ -506,7 +491,7 @@ namespace ESM case RefIdType::Generated: { std::uint64_t generated{}; - getExact(&generated, sizeof(std::uint64_t)); + getT(generated); return RefId::generated(generated); } case RefIdType::Index: @@ -514,14 +499,14 @@ namespace ESM RecNameInts recordType{}; getExact(&recordType, sizeof(std::uint32_t)); std::uint32_t index{}; - getExact(&index, sizeof(std::uint32_t)); + getT(index); return RefId::index(recordType, index); } case RefIdType::ESM3ExteriorCell: { int32_t x, y; - getExact(&x, sizeof(std::int32_t)); - getExact(&y, sizeof(std::int32_t)); + getT(x); + getT(y); return RefId::esm3ExteriorCell(x, y); } } @@ -529,7 +514,7 @@ namespace ESM fail("Unsupported RefIdType: " + std::to_string(static_cast(refIdType))); } - [[noreturn]] void ESMReader::fail(const std::string& msg) + [[noreturn]] void ESMReader::fail(std::string_view msg) { std::stringstream ss; diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index b67cc0f8bb..5af5e75573 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -237,12 +237,6 @@ namespace ESM void skipHRefId(); - // Read the given number of bytes from a subrecord - void getHExact(void* p, std::size_t size); - - // Read the given number of bytes from a named subrecord - void getHNExact(void* p, std::size_t size, NAME name); - ESM::FormId getFormId(bool wide = false, NAME tag = "FRMR"); /************************************************************************* @@ -354,7 +348,7 @@ namespace ESM } /// Used for error handling - [[noreturn]] void fail(const std::string& msg); + [[noreturn]] void fail(std::string_view msg); /// Sets font encoder for ESM strings void setEncoder(ToUTF8::Utf8Encoder* encoder) { mEncoder = encoder; } diff --git a/components/esm3/esmwriter.cpp b/components/esm3/esmwriter.cpp index ad64ced0a4..47c861e3ca 100644 --- a/components/esm3/esmwriter.cpp +++ b/components/esm3/esmwriter.cpp @@ -97,14 +97,14 @@ namespace ESM mHeader.mData.type = type; } - void ESMWriter::setAuthor(const std::string& auth) + void ESMWriter::setAuthor(std::string_view auth) { - mHeader.mData.author.assign(auth); + mHeader.mData.author = auth; } - void ESMWriter::setDescription(const std::string& desc) + void ESMWriter::setDescription(std::string_view desc) { - mHeader.mData.desc.assign(desc); + mHeader.mData.desc = desc; } void ESMWriter::setRecordCount(int count) @@ -122,7 +122,7 @@ namespace ESM mHeader.mMaster.clear(); } - void ESMWriter::addMaster(const std::string& name, uint64_t size) + void ESMWriter::addMaster(std::string_view name, uint64_t size) { Header::MasterData d; d.name = name; @@ -208,14 +208,14 @@ namespace ESM endRecord(NAME(name)); } - void ESMWriter::writeHNString(NAME name, const std::string& data) + void ESMWriter::writeHNString(NAME name, std::string_view data) { startSubRecord(name); writeHString(data); endRecord(name); } - void ESMWriter::writeHNString(NAME name, const std::string& data, size_t size) + void ESMWriter::writeHNString(NAME name, std::string_view data, size_t size) { assert(data.size() <= size); startSubRecord(name); @@ -278,9 +278,9 @@ namespace ESM write(string.c_str(), string.size()); } - void ESMWriter::writeHString(const std::string& data) + void ESMWriter::writeHString(std::string_view data) { - if (data.size() == 0) + if (data.empty()) write("\0", 1); else { @@ -291,7 +291,7 @@ namespace ESM } } - void ESMWriter::writeHCString(const std::string& data) + void ESMWriter::writeHCString(std::string_view data) { writeHString(data); if (data.size() > 0 && data[data.size() - 1] != '\0') diff --git a/components/esm3/esmwriter.hpp b/components/esm3/esmwriter.hpp index 101246fe43..96445bcdae 100644 --- a/components/esm3/esmwriter.hpp +++ b/components/esm3/esmwriter.hpp @@ -38,8 +38,8 @@ namespace ESM void setVersion(unsigned int ver = 0x3fa66666); void setType(int type); void setEncoder(ToUTF8::Utf8Encoder* encoding); - void setAuthor(const std::string& author); - void setDescription(const std::string& desc); + void setAuthor(std::string_view author); + void setDescription(std::string_view desc); void setHeader(const Header& value) { mHeader = value; } // Set the record count for writing it in the file header @@ -54,7 +54,7 @@ namespace ESM void clearMaster(); - void addMaster(const std::string& name, uint64_t size); + void addMaster(std::string_view name, uint64_t size); void save(std::ostream& file); ///< Start saving a file by writing the TES3 header. @@ -62,20 +62,20 @@ namespace ESM void close(); ///< \note Does not close the stream. - void writeHNString(NAME name, const std::string& data); - void writeHNString(NAME name, const std::string& data, size_t size); - void writeHNCString(NAME name, const std::string& data) + void writeHNString(NAME name, std::string_view data); + void writeHNString(NAME name, std::string_view data, size_t size); + void writeHNCString(NAME name, std::string_view data) { startSubRecord(name); writeHCString(data); endRecord(name); } - void writeHNOString(NAME name, const std::string& data) + void writeHNOString(NAME name, std::string_view data) { if (!data.empty()) writeHNString(name, data); } - void writeHNOCString(NAME name, const std::string& data) + void writeHNOCString(NAME name, std::string_view data) { if (!data.empty()) writeHNCString(name, data); @@ -140,6 +140,7 @@ namespace ESM // state being discarded without any error on writing or reading it. :( // writeHNString and friends must be used instead. void writeHNT(NAME name, const std::string& data) = delete; + void writeHNT(NAME name, std::string_view data) = delete; void writeT(NAME data) = delete; @@ -181,8 +182,8 @@ namespace ESM void endRecord(NAME name); void endRecord(uint32_t name); void writeMaybeFixedSizeString(const std::string& data, std::size_t size); - void writeHString(const std::string& data); - void writeHCString(const std::string& data); + void writeHString(std::string_view data); + void writeHCString(std::string_view data); void writeMaybeFixedSizeRefId(RefId value, std::size_t size); diff --git a/components/esm3/landrecorddata.hpp b/components/esm3/landrecorddata.hpp index e7db0d9f3a..ca2a2b74ad 100644 --- a/components/esm3/landrecorddata.hpp +++ b/components/esm3/landrecorddata.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_ESM3_LANDRECORDDATA_H #define OPENMW_COMPONENTS_ESM3_LANDRECORDDATA_H +#include #include namespace ESM @@ -22,24 +23,20 @@ namespace ESM // Initial reference height for the first vertex, only needed for filling mHeights float mHeightOffset = 0; // Height in world space for each vertex - float mHeights[sLandNumVerts]; + std::array mHeights; float mMinHeight = 0; float mMaxHeight = 0; // 24-bit normals, these aren't always correct though. Edge and corner normals may be garbage. - std::int8_t mNormals[sLandNumVerts * 3]; + std::array mNormals; // 2D array of texture indices. An index can be used to look up an LandTexture, // but to do so you must subtract 1 from the index first! // An index of 0 indicates the default texture. - std::uint16_t mTextures[sLandNumTextures]; + std::array mTextures; // 24-bit RGB color for each vertex - std::uint8_t mColours[3 * sLandNumVerts]; - - // ??? - std::uint16_t mUnk1 = 0; - std::uint8_t mUnk2 = 0; + std::array mColours; int mDataLoaded = 0; }; diff --git a/components/esm3/loadland.cpp b/components/esm3/loadland.cpp index 8b8a8f90fa..74edf30498 100644 --- a/components/esm3/loadland.cpp +++ b/components/esm3/loadland.cpp @@ -5,7 +5,9 @@ #include #include -#include "components/esm/defs.hpp" +#include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" @@ -13,27 +15,43 @@ namespace ESM { namespace { + struct VHGT + { + float mHeightOffset; + std::int8_t mHeightData[LandRecordData::sLandNumVerts]; + }; + + template T> + void decompose(T&& v, const auto& f) + { + char padding[3] = { 0, 0, 0 }; + f(v.mHeightOffset, v.mHeightData, padding); + } + void transposeTextureData(const std::uint16_t* in, std::uint16_t* out) { - int readPos = 0; // bit ugly, but it works - for (int y1 = 0; y1 < 4; y1++) - for (int x1 = 0; x1 < 4; x1++) - for (int y2 = 0; y2 < 4; y2++) - for (int x2 = 0; x2 < 4; x2++) + size_t readPos = 0; // bit ugly, but it works + for (size_t y1 = 0; y1 < 4; y1++) + for (size_t x1 = 0; x1 < 4; x1++) + for (size_t y2 = 0; y2 < 4; y2++) + for (size_t x2 = 0; x2 < 4; x2++) out[(y1 * 4 + y2) * 16 + (x1 * 4 + x2)] = in[readPos++]; } // Loads data and marks it as loaded. Return true if data is actually loaded from reader, false otherwise // including the case when data is already loaded. - bool condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, void* ptr, std::size_t size) + bool condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, auto& in) { if ((targetFlags & dataFlag) == 0 && (flags & dataFlag) != 0) { - reader.getHExact(ptr, size); + if constexpr (std::is_same_v, VHGT>) + reader.getSubComposite(in); + else + reader.getHT(in); targetFlags |= dataFlag; return true; } - reader.skipHSubSize(size); + reader.skipHSub(); return false; } } @@ -50,11 +68,7 @@ namespace ESM switch (esm.retSubName().toInt()) { case fourCC("INTV"): - esm.getSubHeader(); - if (esm.getSubSize() != 8) - esm.fail("Subrecord size is not equal to 8"); - esm.getT(mX); - esm.getT(mY); + esm.getHT(mX, mY); hasLocation = true; break; case fourCC("DATA"): @@ -94,7 +108,7 @@ namespace ESM mDataTypes |= DATA_VHGT; break; case fourCC("WNAM"): - esm.getHExact(mWnam.data(), mWnam.size()); + esm.getHT(mWnam); mDataTypes |= DATA_WNAM; break; case fourCC("VCLR"): @@ -137,12 +151,10 @@ namespace ESM { VHGT offsets; offsets.mHeightOffset = mLandData->mHeights[0] / HEIGHT_SCALE; - offsets.mUnk1 = mLandData->mUnk1; - offsets.mUnk2 = mLandData->mUnk2; float prevY = mLandData->mHeights[0]; - int number = 0; // avoid multiplication - for (int i = 0; i < LAND_SIZE; ++i) + size_t number = 0; // avoid multiplication + for (unsigned i = 0; i < LandRecordData::sLandSize; ++i) { float diff = (mLandData->mHeights[number] - prevY) / HEIGHT_SCALE; offsets.mHeightData[number] @@ -151,7 +163,7 @@ namespace ESM float prevX = prevY = mLandData->mHeights[number]; ++number; - for (int j = 1; j < LAND_SIZE; ++j) + for (unsigned j = 1; j < LandRecordData::sLandSize; ++j) { diff = (mLandData->mHeights[number] - prevX) / HEIGHT_SCALE; offsets.mHeightData[number] @@ -161,7 +173,7 @@ namespace ESM ++number; } } - esm.writeHNT("VHGT", offsets, sizeof(VHGT)); + esm.writeNamedComposite("VHGT", offsets); } if (mDataTypes & Land::DATA_WNAM) { @@ -169,13 +181,15 @@ namespace ESM std::int8_t wnam[LAND_GLOBAL_MAP_LOD_SIZE]; constexpr float max = std::numeric_limits::max(); constexpr float min = std::numeric_limits::min(); - constexpr float vertMult = static_cast(Land::LAND_SIZE - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT; - for (int row = 0; row < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++row) + constexpr float vertMult + = static_cast(LandRecordData::sLandSize - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT; + for (unsigned row = 0; row < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++row) { - for (int col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col) + for (unsigned col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col) { - float height = mLandData->mHeights[static_cast(row * vertMult) * Land::LAND_SIZE - + static_cast(col * vertMult)]; + float height + = mLandData->mHeights[static_cast(row * vertMult) * LandRecordData::sLandSize + + static_cast(col * vertMult)]; height /= height > 0 ? 128.f : 16.f; height = std::clamp(height, min, max); wnam[row * LAND_GLOBAL_MAP_LOD_SIZE_SQRT + col] = static_cast(height); @@ -189,8 +203,8 @@ namespace ESM } if (mDataTypes & Land::DATA_VTEX) { - uint16_t vtex[LAND_NUM_TEXTURES]; - transposeTextureData(mLandData->mTextures, vtex); + uint16_t vtex[LandRecordData::sLandNumTextures]; + transposeTextureData(mLandData->mTextures.data(), vtex); esm.writeHNT("VTEX", vtex); } } @@ -200,25 +214,23 @@ namespace ESM { setPlugin(0); - std::fill(std::begin(mWnam), std::end(mWnam), 0); + mWnam.fill(0); if (mLandData == nullptr) mLandData = std::make_unique(); mLandData->mHeightOffset = 0; - std::fill(std::begin(mLandData->mHeights), std::end(mLandData->mHeights), 0.0f); + mLandData->mHeights.fill(0); mLandData->mMinHeight = 0; mLandData->mMaxHeight = 0; - for (int i = 0; i < LAND_NUM_VERTS; ++i) + for (size_t i = 0; i < LandRecordData::sLandNumVerts; ++i) { mLandData->mNormals[i * 3 + 0] = 0; mLandData->mNormals[i * 3 + 1] = 0; mLandData->mNormals[i * 3 + 2] = 127; } - std::fill(std::begin(mLandData->mTextures), std::end(mLandData->mTextures), 0); - std::fill(std::begin(mLandData->mColours), std::end(mLandData->mColours), 255); - mLandData->mUnk1 = 0; - mLandData->mUnk2 = 0; + mLandData->mTextures.fill(0); + mLandData->mColours.fill(255); mLandData->mDataLoaded = Land::DATA_VNML | Land::DATA_VHGT | Land::DATA_WNAM | Land::DATA_VCLR | Land::DATA_VTEX; mDataTypes = mLandData->mDataLoaded; @@ -259,32 +271,32 @@ namespace ESM if (reader.isNextSub("VNML")) { - condLoad(reader, flags, data.mDataLoaded, DATA_VNML, data.mNormals, sizeof(data.mNormals)); + condLoad(reader, flags, data.mDataLoaded, DATA_VNML, data.mNormals); } if (reader.isNextSub("VHGT")) { VHGT vhgt; - if (condLoad(reader, flags, data.mDataLoaded, DATA_VHGT, &vhgt, sizeof(vhgt))) + if (condLoad(reader, flags, data.mDataLoaded, DATA_VHGT, vhgt)) { data.mMinHeight = std::numeric_limits::max(); data.mMaxHeight = -std::numeric_limits::max(); float rowOffset = vhgt.mHeightOffset; - for (int y = 0; y < LAND_SIZE; y++) + for (unsigned y = 0; y < LandRecordData::sLandSize; y++) { - rowOffset += vhgt.mHeightData[y * LAND_SIZE]; + rowOffset += vhgt.mHeightData[y * LandRecordData::sLandSize]; - data.mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE; + data.mHeights[y * LandRecordData::sLandSize] = rowOffset * HEIGHT_SCALE; if (rowOffset * HEIGHT_SCALE > data.mMaxHeight) data.mMaxHeight = rowOffset * HEIGHT_SCALE; if (rowOffset * HEIGHT_SCALE < data.mMinHeight) data.mMinHeight = rowOffset * HEIGHT_SCALE; float colOffset = rowOffset; - for (int x = 1; x < LAND_SIZE; x++) + for (unsigned x = 1; x < LandRecordData::sLandSize; x++) { - colOffset += vhgt.mHeightData[y * LAND_SIZE + x]; - data.mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; + colOffset += vhgt.mHeightData[y * LandRecordData::sLandSize + x]; + data.mHeights[x + y * LandRecordData::sLandSize] = colOffset * HEIGHT_SCALE; if (colOffset * HEIGHT_SCALE > data.mMaxHeight) data.mMaxHeight = colOffset * HEIGHT_SCALE; @@ -292,8 +304,6 @@ namespace ESM data.mMinHeight = colOffset * HEIGHT_SCALE; } } - data.mUnk1 = vhgt.mUnk1; - data.mUnk2 = vhgt.mUnk2; } } @@ -301,13 +311,13 @@ namespace ESM reader.skipHSub(); if (reader.isNextSub("VCLR")) - condLoad(reader, flags, data.mDataLoaded, DATA_VCLR, data.mColours, 3 * LAND_NUM_VERTS); + condLoad(reader, flags, data.mDataLoaded, DATA_VCLR, data.mColours); if (reader.isNextSub("VTEX")) { - uint16_t vtex[LAND_NUM_TEXTURES]; - if (condLoad(reader, flags, data.mDataLoaded, DATA_VTEX, vtex, sizeof(vtex))) + uint16_t vtex[LandRecordData::sLandNumTextures]; + if (condLoad(reader, flags, data.mDataLoaded, DATA_VTEX, vtex)) { - transposeTextureData(vtex, data.mTextures); + transposeTextureData(vtex, data.mTextures.data()); } } } diff --git a/components/esm3/loadland.hpp b/components/esm3/loadland.hpp index 0d32407a5d..510f1790a8 100644 --- a/components/esm3/loadland.hpp +++ b/components/esm3/loadland.hpp @@ -86,19 +86,9 @@ namespace ESM // total number of textures per land static constexpr int LAND_NUM_TEXTURES = LandRecordData::sLandNumTextures; - static constexpr int LAND_GLOBAL_MAP_LOD_SIZE = 81; + static constexpr unsigned LAND_GLOBAL_MAP_LOD_SIZE = 81; - static constexpr int LAND_GLOBAL_MAP_LOD_SIZE_SQRT = 9; - -#pragma pack(push, 1) - struct VHGT - { - float mHeightOffset; - std::int8_t mHeightData[LAND_NUM_VERTS]; - std::uint16_t mUnk1; - std::uint8_t mUnk2; - }; -#pragma pack(pop) + static constexpr unsigned LAND_GLOBAL_MAP_LOD_SIZE_SQRT = 9; using LandData = ESM::LandRecordData; @@ -128,16 +118,10 @@ namespace ESM const LandData* getLandData(int flags) const; /// Return land data without loading first anything. Can return a 0-pointer. - const LandData* getLandData() const - { - return mLandData.get(); - } + const LandData* getLandData() const { return mLandData.get(); } /// Return land data without loading first anything. Can return a 0-pointer. - LandData* getLandData() - { - return mLandData.get(); - } + LandData* getLandData() { return mLandData.get(); } /// \attention Must not be called on objects that aren't fully loaded. /// From 24913687cde83ecd6ee6b1b72dd8e8d0e77cfc0f Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 23 Mar 2024 09:15:12 +0300 Subject: [PATCH 298/451] Exterior cell naming corrections Use the ID for anonymous regions Try to use the name of the worldspace for ESM4 --- apps/openmw/mwbase/world.hpp | 2 -- apps/openmw/mwworld/worldimp.cpp | 37 ++++++++++++++++++-------------- apps/openmw/mwworld/worldimp.hpp | 1 - 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index fe8b5cc13a..b800311eca 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -183,8 +183,6 @@ namespace MWBase /// generate a name. virtual std::string_view getCellName(const MWWorld::Cell& cell) const = 0; - virtual std::string_view getCellName(const ESM::Cell* cell) const = 0; - virtual void removeRefScript(const MWWorld::CellRef* ref) = 0; //< Remove the script attached to ref from mLocalScripts diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0468b36a2f..ed61cf5bde 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -662,25 +663,29 @@ namespace MWWorld if (!cell.isExterior() || !cell.getDisplayName().empty()) return cell.getDisplayName(); - return ESM::visit(ESM::VisitOverload{ - [&](const ESM::Cell& cellIn) -> std::string_view { return getCellName(&cellIn); }, - [&](const ESM4::Cell& cellIn) -> std::string_view { - return mStore.get().find("sDefaultCellname")->mValue.getString(); - }, - }, - cell); - } - - std::string_view World::getCellName(const ESM::Cell* cell) const - { - if (cell) + if (!cell.getRegion().empty()) { - if (!cell->isExterior() || !cell->mName.empty()) - return cell->mName; + std::string_view regionName + = ESM::visit(ESM::VisitOverload{ + [&](const ESM::Cell& cellIn) -> std::string_view { + if (const ESM::Region* region = mStore.get().search(cell.getRegion())) + return !region->mName.empty() ? region->mName : region->mId.getRefIdString(); + return {}; + }, + [&](const ESM4::Cell& cellIn) -> std::string_view { return {}; }, + }, + cell); + if (!regionName.empty()) + return regionName; + } - if (const ESM::Region* region = mStore.get().search(cell->mRegion)) - return region->mName; + if (!cell.getWorldSpace().empty() && ESM::isEsm4Ext(cell.getWorldSpace())) + { + if (const ESM4::World* worldspace = mStore.get().search(cell.getWorldSpace())) + if (!worldspace->mFullName.empty()) + return worldspace->mFullName; } + return mStore.get().find("sDefaultCellname")->mValue.getString(); } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4e36419e7f..b7db68214d 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -273,7 +273,6 @@ namespace MWWorld /// \note If cell==0, the cell the player is currently in will be used instead to /// generate a name. std::string_view getCellName(const MWWorld::Cell& cell) const override; - std::string_view getCellName(const ESM::Cell* cell) const override; void removeRefScript(const MWWorld::CellRef* ref) override; //< Remove the script attached to ref from mLocalScripts From c5c80936a0220ed41ca199eab881976968359d05 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 23 Mar 2024 13:27:53 -0500 Subject: [PATCH 299/451] Space after , --- .../data/integration_tests/test_lua_api/test.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index 775f3ba499..53262dd168 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -68,23 +68,23 @@ end local function testMWScript() local variableStoreCount = 18 local variableStore = world.mwscript.getGlobalVariables(player) - testing.expectEqual(variableStoreCount,#variableStore) + testing.expectEqual(variableStoreCount, #variableStore) - variableStore.year = variableStoreCount - testing.expectEqual(variableStoreCount,variableStore.year) + variableStore.year = 5 + testing.expectEqual(5, variableStore.year) variableStore.year = 1 local indexCheck = 0 for index, value in ipairs(variableStore) do - testing.expectEqual(variableStore[index],value) + testing.expectEqual(variableStore[index], value) indexCheck = indexCheck + 1 end - testing.expectEqual(variableStoreCount,indexCheck) + testing.expectEqual(variableStoreCount, indexCheck) indexCheck = 0 for index, value in pairs(variableStore) do - testing.expectEqual(variableStore[index],value) + testing.expectEqual(variableStore[index], value) indexCheck = indexCheck + 1 end - testing.expectEqual(variableStoreCount,indexCheck) + testing.expectEqual(variableStoreCount, indexCheck) end local function initPlayer() From a4dd9224df6cb49ee9848ddf6284f21981ace72f Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Sat, 23 Mar 2024 21:56:30 +0000 Subject: [PATCH 300/451] Restructure colormasks at higher level --- apps/openmw/mwrender/animation.cpp | 4 +--- apps/openmw/mwrender/npcanimation.cpp | 11 ++--------- apps/openmw/mwrender/renderingmanager.cpp | 2 ++ apps/openmw/mwrender/sky.cpp | 1 + apps/openmw/mwrender/skyutil.cpp | 4 +--- components/resource/scenemanager.cpp | 8 ++++++++ components/resource/scenemanager.hpp | 2 ++ 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 9cdbb19a98..6d4456699b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -1594,8 +1593,7 @@ namespace MWRender // Morrowind has a white ambient light attached to the root VFX node of the scenegraph node->getOrCreateStateSet()->setAttributeAndModes( getVFXLightModelInstance(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); - if (mResourceSystem->getSceneManager()->getSupportsNormalsRT()) - node->getOrCreateStateSet()->setAttribute(new osg::ColorMaski(1, false, false, false, false)); + mResourceSystem->getSceneManager()->setUpNormalsRTForStateSet(node->getOrCreateStateSet(), false); SceneUtil::FindMaxControllerLengthVisitor findMaxLengthVisitor; node->accept(findMaxLengthVisitor); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 61260e687e..b9ad471bf5 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -312,9 +312,8 @@ namespace MWRender class DepthClearCallback : public osgUtil::RenderBin::DrawCallback { public: - DepthClearCallback(Resource::ResourceSystem* resourceSystem) + DepthClearCallback() { - mPassNormals = resourceSystem->getSceneManager()->getSupportsNormalsRT(); mDepth = new SceneUtil::AutoDepth; mDepth->setWriteMask(true); @@ -335,11 +334,6 @@ namespace MWRender unsigned int frameId = state->getFrameStamp()->getFrameNumber() % 2; postProcessor->getFbo(PostProcessor::FBO_FirstPerson, frameId)->apply(*state); - if (mPassNormals) - { - state->get()->glColorMaski(1, true, true, true, true); - state->haveAppliedAttribute(osg::StateAttribute::COLORMASK); - } glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // color accumulation pass bin->drawImplementation(renderInfo, previous); @@ -360,7 +354,6 @@ namespace MWRender state->checkGLErrors("after DepthClearCallback::drawImplementation"); } - bool mPassNormals; osg::ref_ptr mDepth; osg::ref_ptr mStateSet; }; @@ -409,7 +402,7 @@ namespace MWRender if (!prototypeAdded) { osg::ref_ptr depthClearBin(new osgUtil::RenderBin); - depthClearBin->setDrawCallback(new DepthClearCallback(mResourceSystem)); + depthClearBin->setDrawCallback(new DepthClearCallback()); osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); prototypeAdded = true; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 004b041336..acc8976219 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -550,6 +550,8 @@ namespace MWRender sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("distortionStrength", 0.f)); + resourceSystem->getSceneManager()->setUpNormalsRTForStateSet(sceneRoot->getOrCreateStateSet(), true); + mFog = std::make_unique(); mSky = std::make_unique( diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 231f90fd78..c75849d532 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -274,6 +274,7 @@ namespace MWRender if (!mSceneManager->getForceShaders()) skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON); + mSceneManager->setUpNormalsRTForStateSet(skyroot->getOrCreateStateSet(), false); SceneUtil::ShadowManager::instance().disableShadowsForStateSet(*skyroot->getOrCreateStateSet()); parentNode->addChild(skyroot); diff --git a/apps/openmw/mwrender/skyutil.cpp b/apps/openmw/mwrender/skyutil.cpp index 3274b8c6b0..53baf36416 100644 --- a/apps/openmw/mwrender/skyutil.cpp +++ b/apps/openmw/mwrender/skyutil.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -794,8 +793,7 @@ namespace MWRender // Disable writing to the color buffer. We are using this geometry for visibility tests only. osg::ref_ptr colormask = new osg::ColorMask(0, 0, 0, 0); stateset->setAttributeAndModes(colormask); - if (sceneManager.getSupportsNormalsRT()) - stateset->setAttributeAndModes(new osg::ColorMaski(1, false, false, false, false)); + sceneManager.setUpNormalsRTForStateSet(stateset, false); mTransform->addChild(queryNode); mOcclusionQueryVisiblePixels = createOcclusionQueryNode(queryNode, true); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 9ed72d5f05..45c84f093f 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -511,6 +512,13 @@ namespace Resource return mCache->checkInObjectCache(VFS::Path::normalizeFilename(name), timeStamp); } + void SceneManager::setUpNormalsRTForStateSet(osg::StateSet* stateset, bool enabled) + { + if (!getSupportsNormalsRT()) + return; + stateset->setAttributeAndModes(new osg::ColorMaski(1, enabled, enabled, enabled, enabled)); + } + /// @brief Callback to read image files from the VFS. class ImageReadCallback : public osgDB::ReadFileCallback { diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 12900441de..3ad8a24892 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -224,6 +224,8 @@ namespace Resource void setSupportsNormalsRT(bool supports) { mSupportsNormalsRT = supports; } bool getSupportsNormalsRT() const { return mSupportsNormalsRT; } + void setUpNormalsRTForStateSet(osg::StateSet* stateset, bool enabled); + void setSoftParticles(bool enabled) { mSoftParticles = enabled; } bool getSoftParticles() const { return mSoftParticles; } From 0f7b4fc6e693b34ecfa5b3191cead42a717ac504 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 24 Mar 2024 13:40:34 +0300 Subject: [PATCH 301/451] Consistently avoid null pointer dereferencing in postprocessor (#7587) --- CHANGELOG.md | 1 + apps/openmw/mwrender/postprocessor.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a83d365845..1395c1cc40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -111,6 +111,7 @@ Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading Bug #7573: Drain Fatigue can't bring fatigue below zero by default Bug #7585: Difference in interior lighting between OpenMW with legacy lighting method enabled and vanilla Morrowind + Bug #7587: Quick load related crash Bug #7603: Scripts menu size is not updated properly Bug #7604: Goblins Grunt becomes idle once injured Bug #7609: ForceGreeting should not open dialogue for werewolves diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 42b2e4e1ee..1c0702879d 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -346,7 +346,7 @@ namespace MWRender for (auto& technique : mTechniques) { - if (technique->getStatus() == fx::Technique::Status::File_Not_exists) + if (!technique || technique->getStatus() == fx::Technique::Status::File_Not_exists) continue; const auto lastWriteTime = std::filesystem::last_write_time(mTechniqueFileMap[technique->getName()]); @@ -564,7 +564,7 @@ namespace MWRender for (const auto& technique : mTechniques) { - if (!technique->isValid()) + if (!technique || !technique->isValid()) continue; if (technique->getGLSLVersion() > mGLSLVersion) @@ -718,7 +718,7 @@ namespace MWRender PostProcessor::Status PostProcessor::disableTechnique(std::shared_ptr technique, bool dirty) { - if (technique->getLocked()) + if (!technique || technique->getLocked()) return Status_Error; auto it = std::find(mTechniques.begin(), mTechniques.end(), technique); @@ -734,6 +734,9 @@ namespace MWRender bool PostProcessor::isTechniqueEnabled(const std::shared_ptr& technique) const { + if (!technique) + return false; + if (auto it = std::find(mTechniques.begin(), mTechniques.end(), technique); it == mTechniques.end()) return false; @@ -815,7 +818,7 @@ namespace MWRender void PostProcessor::disableDynamicShaders() { for (auto& technique : mTechniques) - if (technique->getDynamic()) + if (technique && technique->getDynamic()) disableTechnique(technique); } From ba69e1737c46eb7aecb7e0dd09b9a9a76a463777 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 24 Mar 2024 13:45:46 +0300 Subject: [PATCH 302/451] Use the right shader for 360-degree screenshots Doesn't fix #7720 --- apps/openmw/mwrender/screenshotmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index a23d242a15..f478229daa 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -243,7 +243,7 @@ namespace MWRender osg::ref_ptr stateset = quad->getOrCreateStateSet(); Shader::ShaderManager& shaderMgr = mResourceSystem->getSceneManager()->getShaderManager(); - stateset->setAttributeAndModes(shaderMgr.getProgram("360"), osg::StateAttribute::ON); + stateset->setAttributeAndModes(shaderMgr.getProgram("s360"), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("cubeMap", 0)); stateset->addUniform(new osg::Uniform("mapping", static_cast(screenshotMapping))); From 6515fdd73fbd42f094b34300499d233a8955b6c6 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 24 Mar 2024 19:58:22 +0300 Subject: [PATCH 303/451] Handle zero length Lua storage files more gracefully (#7823) --- CHANGELOG.md | 1 + components/lua/storage.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a83d365845..7f70ea9f90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -154,6 +154,7 @@ Bug #7785: OpenMW-CS initialising Skill and Attribute fields to 0 instead of -1 on non-FortifyStat spells Bug #7794: Fleeing NPCs name tooltip doesn't appear Bug #7796: Absorbed enchantments don't restore magicka + Bug #7823: Game crashes when launching it. Bug #7832: Ingredient tooltips show magnitude for Fortify Maximum Magicka effect Bug #7840: First run of the launcher doesn't save viewing distance as the default value Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs diff --git a/components/lua/storage.cpp b/components/lua/storage.cpp index db81b6e172..063dbf0d10 100644 --- a/components/lua/storage.cpp +++ b/components/lua/storage.cpp @@ -239,8 +239,11 @@ namespace LuaUtil assert(mData.empty()); // Shouldn't be used before loading try { - Log(Debug::Info) << "Loading Lua storage \"" << path << "\" (" << std::filesystem::file_size(path) - << " bytes)"; + std::uintmax_t fileSize = std::filesystem::file_size(path); + Log(Debug::Info) << "Loading Lua storage \"" << path << "\" (" << fileSize << " bytes)"; + if (fileSize == 0) + throw std::runtime_error("Storage file has zero length"); + std::ifstream fin(path, std::fstream::binary); std::string serializedData((std::istreambuf_iterator(fin)), std::istreambuf_iterator()); sol::table data = deserialize(mLua, serializedData); @@ -253,7 +256,7 @@ namespace LuaUtil } catch (std::exception& e) { - Log(Debug::Error) << "Can not read \"" << path << "\": " << e.what(); + Log(Debug::Error) << "Cannot read \"" << path << "\": " << e.what(); } } From c59d097ab2366c44a69b1380781876ba86c4e7db Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Sun, 24 Mar 2024 16:24:49 -0500 Subject: [PATCH 304/451] FIX(#7898): Limit scale for references TES3 values --- CHANGELOG.md | 1 + apps/opencs/model/world/columnimp.hpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a83d365845..4df27121bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -160,6 +160,7 @@ Bug #7859: AutoCalc flag is not used to calculate potion value Bug #7872: Region sounds use wrong odds Bug #7887: Editor: Mismatched reported script data size and actual data size causes a crash during save + Bug #7898: Editor: Invalid reference scales are allowed Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 0cd95b0ed2..53e0ba07cf 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -971,7 +971,7 @@ namespace CSMWorld void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mScale = data.toFloat(); + record2.mScale = std::clamp(data.toFloat(), 0.5f, 2.0f); record.setModified(record2); } From 0e2f28156da92b9a6b959c614f88606b32b40fa6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 24 Mar 2024 23:48:37 +0000 Subject: [PATCH 305/451] Restore logging of openmw.cfg paths in launcher Removed here https://gitlab.com/OpenMW/openmw/-/merge_requests/2650/diffs#be09c16519a3f26f4306b920c50e0e4215dffaee_329_328 --- apps/launcher/maindialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 178b254545..5424d4010b 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -351,6 +351,7 @@ bool Launcher::MainDialog::setupGameSettings() for (const auto& path : Files::getActiveConfigPathsQString(mCfgMgr)) { + Log(Debug::Verbose) << "Loading config file: " << path.toUtf8().constData(); if (!loadFile(path, &Config::GameSettings::readFile)) return false; } From 6d529835aeb2a7f7ae2f7e76ba5013f80e0c820a Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Mon, 25 Mar 2024 13:46:23 +0000 Subject: [PATCH 306/451] Lua: Standardize record stores --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/corebindings.cpp | 3 +- apps/openmw/mwlua/factionbindings.cpp | 28 +-- apps/openmw/mwlua/factionbindings.hpp | 2 +- apps/openmw/mwlua/magicbindings.cpp | 57 ++---- apps/openmw/mwlua/recordstore.hpp | 63 ++++++ apps/openmw/mwlua/soundbindings.cpp | 20 +- apps/openmw/mwlua/stats.cpp | 2 +- apps/openmw/mwlua/types/types.hpp | 46 +---- files/lua_api/openmw/core.lua | 67 ++++--- files/lua_api/openmw/types.lua | 180 +++++++++++++++--- .../integration_tests/test_lua_api/test.lua | 41 ++++ 12 files changed, 329 insertions(+), 182 deletions(-) create mode 100644 apps/openmw/mwlua/recordstore.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index e69bb5f240..f92e8a0bc1 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -63,7 +63,7 @@ add_openmw_dir (mwlua luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings - postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings + postprocessingbindings stats recordstore debugbindings corebindings worldbindings worker magicbindings factionbindings classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings markupbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 8d8e97ed07..b212d4d01c 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -97,8 +97,7 @@ namespace MWLua api["magic"] = initCoreMagicBindings(context); api["stats"] = initCoreStatsBindings(context); - initCoreFactionBindings(context); - api["factions"] = &MWBase::Environment::get().getESMStore()->get(); + api["factions"] = initCoreFactionBindings(context); api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); const MWWorld::Store* gmstStore diff --git a/apps/openmw/mwlua/factionbindings.cpp b/apps/openmw/mwlua/factionbindings.cpp index b606d1a6f9..83b9cfc5e8 100644 --- a/apps/openmw/mwlua/factionbindings.cpp +++ b/apps/openmw/mwlua/factionbindings.cpp @@ -1,4 +1,5 @@ #include "factionbindings.hpp" +#include "recordstore.hpp" #include #include @@ -32,10 +33,6 @@ namespace sol { }; template <> - struct is_automagical> : std::false_type - { - }; - template <> struct is_automagical> : std::false_type { }; @@ -43,27 +40,11 @@ namespace sol namespace MWLua { - using FactionStore = MWWorld::Store; - - void initCoreFactionBindings(const Context& context) + sol::table initCoreFactionBindings(const Context& context) { sol::state_view& lua = context.mLua->sol(); - sol::usertype factionStoreT = lua.new_usertype("ESM3_FactionStore"); - factionStoreT[sol::meta_function::to_string] = [](const FactionStore& store) { - return "ESM3_FactionStore{" + std::to_string(store.getSize()) + " factions}"; - }; - factionStoreT[sol::meta_function::length] = [](const FactionStore& store) { return store.getSize(); }; - factionStoreT[sol::meta_function::index] = sol::overload( - [](const FactionStore& store, size_t index) -> const ESM::Faction* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); - }, - [](const FactionStore& store, std::string_view factionId) -> const ESM::Faction* { - return store.search(ESM::RefId::deserializeText(factionId)); - }); - factionStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - factionStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + sol::table factions(lua, sol::create); + addRecordFunctionBinding(factions, context); // Faction record auto factionT = lua.new_usertype("ESM3_Faction"); factionT[sol::meta_function::to_string] @@ -113,5 +94,6 @@ namespace MWLua res.add(rec.mAttribute2); return res; }); + return LuaUtil::makeReadOnly(factions); } } diff --git a/apps/openmw/mwlua/factionbindings.hpp b/apps/openmw/mwlua/factionbindings.hpp index fe37133dbe..0dc06ceaf2 100644 --- a/apps/openmw/mwlua/factionbindings.hpp +++ b/apps/openmw/mwlua/factionbindings.hpp @@ -7,7 +7,7 @@ namespace MWLua { - void initCoreFactionBindings(const Context& context); + sol::table initCoreFactionBindings(const Context& context); } #endif // MWLUA_FACTIONBINDINGS_H diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 1e3cb2ab69..9dae9f085f 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -31,6 +31,7 @@ #include "luamanagerimp.hpp" #include "object.hpp" #include "objectvariant.hpp" +#include "recordstore.hpp" namespace MWLua { @@ -135,10 +136,6 @@ namespace MWLua namespace sol { - template - struct is_automagical> : std::false_type - { - }; template <> struct is_automagical : std::false_type { @@ -228,50 +225,18 @@ namespace MWLua } // Spell store - using SpellStore = MWWorld::Store; - const SpellStore* spellStore = &MWBase::Environment::get().getWorld()->getStore().get(); - sol::usertype spellStoreT = lua.new_usertype("ESM3_SpellStore"); - spellStoreT[sol::meta_function::to_string] - = [](const SpellStore& store) { return "ESM3_SpellStore{" + std::to_string(store.getSize()) + " spells}"; }; - spellStoreT[sol::meta_function::length] = [](const SpellStore& store) { return store.getSize(); }; - spellStoreT[sol::meta_function::index] = sol::overload( - [](const SpellStore& store, size_t index) -> const ESM::Spell* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); - }, - [](const SpellStore& store, std::string_view spellId) -> const ESM::Spell* { - return store.search(ESM::RefId::deserializeText(spellId)); - }); - spellStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - spellStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); - - magicApi["spells"] = spellStore; + sol::table spells(lua, sol::create); + addRecordFunctionBinding(spells, context); + magicApi["spells"] = LuaUtil::makeReadOnly(spells); // Enchantment store - using EnchantmentStore = MWWorld::Store; - const EnchantmentStore* enchantmentStore - = &MWBase::Environment::get().getWorld()->getStore().get(); - sol::usertype enchantmentStoreT = lua.new_usertype("ESM3_EnchantmentStore"); - enchantmentStoreT[sol::meta_function::to_string] = [](const EnchantmentStore& store) { - return "ESM3_EnchantmentStore{" + std::to_string(store.getSize()) + " enchantments}"; - }; - enchantmentStoreT[sol::meta_function::length] = [](const EnchantmentStore& store) { return store.getSize(); }; - enchantmentStoreT[sol::meta_function::index] = sol::overload( - [](const EnchantmentStore& store, size_t index) -> const ESM::Enchantment* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); - }, - [](const EnchantmentStore& store, std::string_view enchantmentId) -> const ESM::Enchantment* { - return store.search(ESM::RefId::deserializeText(enchantmentId)); - }); - enchantmentStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - enchantmentStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); - - magicApi["enchantments"] = enchantmentStore; + sol::table enchantments(lua, sol::create); + addRecordFunctionBinding(enchantments, context); + magicApi["enchantments"] = LuaUtil::makeReadOnly(enchantments); // MagicEffect store + sol::table magicEffects(lua, sol::create); + magicApi["effects"] = LuaUtil::makeReadOnly(magicEffects); using MagicEffectStore = MWWorld::Store; const MagicEffectStore* magicEffectStore = &MWBase::Environment::get().getWorld()->getStore().get(); @@ -303,8 +268,10 @@ namespace MWLua }; magicEffectStoreT[sol::meta_function::pairs] = [iter = sol::make_object(lua, magicEffectsIter)] { return iter; }; + magicEffectStoreT[sol::meta_function::ipairs] + = [iter = sol::make_object(lua, magicEffectsIter)] { return iter; }; - magicApi["effects"] = magicEffectStore; + magicEffects["records"] = magicEffectStore; // Spell record auto spellT = lua.new_usertype("ESM3_Spell"); diff --git a/apps/openmw/mwlua/recordstore.hpp b/apps/openmw/mwlua/recordstore.hpp new file mode 100644 index 0000000000..3d04de396b --- /dev/null +++ b/apps/openmw/mwlua/recordstore.hpp @@ -0,0 +1,63 @@ +#ifndef MWLUA_RECORDSTORE_H +#define MWLUA_RECORDSTORE_H + +#include + +#include +#include + +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwbase/world.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" +#include "apps/openmw/mwworld/store.hpp" + +#include "context.hpp" +#include "object.hpp" + +namespace sol +{ + // Ensure sol does not try to create the automatic Container or usertype bindings for Store. + // They include write operations and we want the store to be read-only. + template + struct is_automagical> : std::false_type + { + }; +} + +namespace MWLua +{ + template + void addRecordFunctionBinding( + sol::table& table, const Context& context, const std::string& recordName = std::string(T::getRecordType())) + { + const MWWorld::Store& store = MWBase::Environment::get().getESMStore()->get(); + + table["record"] = sol::overload([](const Object& obj) -> const T* { return obj.ptr().get()->mBase; }, + [&store](std::string_view id) -> const T* { return store.search(ESM::RefId::deserializeText(id)); }); + + // Define a custom user type for the store. + // Provide the interface of a read-only array. + using StoreT = MWWorld::Store; + sol::state_view& lua = context.mLua->sol(); + sol::usertype storeT = lua.new_usertype(recordName + "WorldStore"); + storeT[sol::meta_function::to_string] = [recordName](const StoreT& store) { + return "{" + std::to_string(store.getSize()) + " " + recordName + " records}"; + }; + storeT[sol::meta_function::length] = [](const StoreT& store) { return store.getSize(); }; + storeT[sol::meta_function::index] = sol::overload( + [](const StoreT& store, size_t index) -> const T* { + if (index == 0 || index > store.getSize()) + return nullptr; + return store.at(index - 1); // Translate from Lua's 1-based indexing. + }, + [](const StoreT& store, std::string_view id) -> const T* { + return store.search(ESM::RefId::deserializeText(id)); + }); + storeT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); + storeT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); + + // Provide access to the store. + table["records"] = &store; + } +} +#endif // MWLUA_RECORDSTORE_H diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index ad4a498153..11ad22873a 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -1,4 +1,5 @@ #include "soundbindings.hpp" +#include "recordstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -196,24 +197,7 @@ namespace MWLua }, []() { return MWBase::Environment::get().getSoundManager()->sayActive(MWWorld::ConstPtr()); }); - using SoundStore = MWWorld::Store; - sol::usertype soundStoreT = lua.new_usertype("ESM3_SoundStore"); - soundStoreT[sol::meta_function::to_string] - = [](const SoundStore& store) { return "ESM3_SoundStore{" + std::to_string(store.getSize()) + " sounds}"; }; - soundStoreT[sol::meta_function::length] = [](const SoundStore& store) { return store.getSize(); }; - soundStoreT[sol::meta_function::index] = sol::overload( - [](const SoundStore& store, size_t index) -> const ESM::Sound* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); - }, - [](const SoundStore& store, std::string_view soundId) -> const ESM::Sound* { - return store.search(ESM::RefId::deserializeText(soundId)); - }); - soundStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - soundStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); - - api["sounds"] = &MWBase::Environment::get().getWorld()->getStore().get(); + addRecordFunctionBinding(api, context); // Sound record auto soundT = lua.new_usertype("ESM3_Sound"); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index eaa1f89d97..ad0f585207 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -22,7 +22,7 @@ #include "../mwworld/esmstore.hpp" #include "objectvariant.hpp" -#include "types/types.hpp" +#include "recordstore.hpp" namespace { diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp index b52846508a..76bd2848e0 100644 --- a/apps/openmw/mwlua/types/types.hpp +++ b/apps/openmw/mwlua/types/types.hpp @@ -6,22 +6,8 @@ #include #include -#include "apps/openmw/mwbase/environment.hpp" -#include "apps/openmw/mwworld/esmstore.hpp" -#include "apps/openmw/mwworld/store.hpp" - #include "../context.hpp" -#include "../object.hpp" - -namespace sol -{ - // Ensure sol does not try to create the automatic Container or usertype bindings for Store. - // They include write operations and we want the store to be read-only. - template - struct is_automagical> : std::false_type - { - }; -} +#include "../recordstore.hpp" namespace MWLua { @@ -68,36 +54,6 @@ namespace MWLua void addESM4DoorBindings(sol::table door, const Context& context); void addESM4TerminalBindings(sol::table term, const Context& context); - - template - void addRecordFunctionBinding( - sol::table& table, const Context& context, const std::string& recordName = std::string(T::getRecordType())) - { - const MWWorld::Store& store = MWBase::Environment::get().getESMStore()->get(); - - table["record"] = sol::overload([](const Object& obj) -> const T* { return obj.ptr().get()->mBase; }, - [&store](std::string_view id) -> const T* { return store.search(ESM::RefId::deserializeText(id)); }); - - // Define a custom user type for the store. - // Provide the interface of a read-only array. - using StoreT = MWWorld::Store; - sol::state_view& lua = context.mLua->sol(); - sol::usertype storeT = lua.new_usertype(recordName + "WorldStore"); - storeT[sol::meta_function::to_string] = [recordName](const StoreT& store) { - return "{" + std::to_string(store.getSize()) + " " + recordName + " records}"; - }; - storeT[sol::meta_function::length] = [](const StoreT& store) { return store.getSize(); }; - storeT[sol::meta_function::index] = [](const StoreT& store, size_t index) -> const T* { - if (index == 0 || index > store.getSize()) - return nullptr; - return store.at(index - 1); // Translate from Lua's 1-based indexing. - }; - storeT[sol::meta_function::pairs] = lua["ipairsForArray"].template get(); - storeT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get(); - - // Provide access to the store. - table["records"] = &store; - } } #endif // MWLUA_TYPES_H diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 66fb817362..86f95c4079 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -10,10 +10,6 @@ -- The revision of OpenMW Lua API. It is an integer that is incremented every time the API is changed. See the actual value at the top of the page. -- @field [parent=#core] #number API_REVISION ---- --- A read-only list of all @{#FactionRecord}s in the world database. --- @field [parent=#core] #list<#FactionRecord> factions - --- -- Terminates the game and quits to the OS. Should be used only for testing purposes. -- @function [parent=#core] quit @@ -622,41 +618,52 @@ -- @field #number Curse Curse -- @field #number Power Power, can be used once a day +--- @{#Spells}: Spells +-- @field [parent=#Magic] #Spells spells --- List of all @{#Spell}s. --- @field [parent=#Magic] #list<#Spell> spells --- @usage local spell = core.magic.spells['thunder fist'] -- get by id --- @usage local spell = core.magic.spells[1] -- get by index +-- @field [parent=#Spells] #list<#Spell> records A read-only list of all @{#Spell} records in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #Spell. +-- @usage local spell = core.magic.spells.records['thunder fist'] -- get by id +-- @usage local spell = core.magic.spells.records[1] -- get by index -- @usage -- Print all powers --- for _, spell in pairs(core.magic.spells) do +-- for _, spell in pairs(core.magic.spells.records) do -- if spell.types == core.magic.SPELL_TYPE.Power then -- print(spell.name) -- end -- end +--- @{#Effects}: Magic Effects +-- @field [parent=#Magic] #Effects effects + --- Map from @{#MagicEffectId} to @{#MagicEffect} --- @field [parent=#Magic] #map<#number, #MagicEffect> effects +-- @field [parent=#Effects] #map<#number, #MagicEffect> records -- @usage -- Print all harmful effects --- for _, effect in pairs(core.magic.effects) do +-- for _, effect in pairs(core.magic.effects.records) do -- if effect.harmful then -- print(effect.name) -- end -- end -- @usage -- Look up the record of a specific effect and print its icon --- local mgef = core.magic.effects[core.magic.EFFECT_TYPE.Reflect] +-- local mgef = core.magic.effects.records[core.magic.EFFECT_TYPE.Reflect] -- print('Reflect Icon: '..tostring(mgef.icon)) ---- List of all @{#Enchantment}s. --- @field [parent=#Magic] #list<#Enchantment> enchantments --- @usage local enchantment = core.magic.enchantments['marara's boon'] -- get by id --- @usage local enchantment = core.magic.enchantments[1] -- get by index +--- @{#Enchantments}: Enchantments +-- @field [parent=#Magic] #Enchantments enchantments + +--- A read-only list of all @{#Enchantment} records in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) and [iterables#Map](iterables.html#map-iterable) of #Enchantment. +-- @field [parent=#Enchantments] #list<#Enchantment> records +-- @usage local enchantment = core.magic.enchantments.records['marara's boon'] -- get by id +-- @usage local enchantment = core.magic.enchantments.records[1] -- get by index -- @usage -- Print all enchantments with constant effect --- for _, ench in pairs(core.magic.enchantments) do +-- for _, ench in pairs(core.magic.enchantments.records) do -- if ench.type == core.magic.ENCHANTMENT_TYPE.ConstantEffect then -- print(ench.id) -- end -- end + --- -- @type Spell -- @field #string id Spell id @@ -827,11 +834,12 @@ -- @field #number maxRange Raw maximal range value, from 0 to 255 --- List of all @{#SoundRecord}s. --- @field [parent=#Sound] #list<#SoundRecord> sounds --- @usage local sound = core.sound.sounds['Ashstorm'] -- get by id --- @usage local sound = core.sound.sounds[1] -- get by index +-- @field [parent=#Sound] #list<#SoundRecord> records A read-only list of all @{#SoundRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #SoundRecord. +-- @usage local sound = core.sound.records['Ashstorm'] -- get by id +-- @usage local sound = core.sound.records[1] -- get by index -- @usage -- Print all sound files paths --- for _, sound in pairs(core.sound.sounds) do +-- for _, sound in pairs(core.sound.records) do -- print(sound.fileName) -- end @@ -844,7 +852,10 @@ --- `core.stats.Attribute` -- @type Attribute --- @field #list<#AttributeRecord> records A read-only list of all @{#AttributeRecord}s in the world database. +-- @field #list<#AttributeRecord> records A read-only list of all @{#AttributeRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #AttributeRecord. +-- @usage local record = core.stats.Attribute.records['example_recordid'] +-- @usage local record = core.stats.Attribute.records[1] --- -- Returns a read-only @{#AttributeRecord} @@ -857,7 +868,10 @@ --- `core.stats.Skill` -- @type Skill --- @field #list<#SkillRecord> records A read-only list of all @{#SkillRecord}s in the world database. +-- @field #list<#SkillRecord> records A read-only list of all @{#SkillRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #SkillRecord. +-- @usage local record = core.stats.Skill.records['example_recordid'] +-- @usage local record = core.stats.Skill.records[1] --- -- Returns a read-only @{#SkillRecord} @@ -892,6 +906,15 @@ -- @field #string failureSound VFS path to the failure sound -- @field #string hitSound VFS path to the hit sound +--- @{#Factions}: Factions +-- @field [parent=#core] #Factions factions + +--- +-- A read-only list of all @{#FactionRecord}s in the world database. +-- @field [parent=#Factions] #list<#FactionRecord> records +-- @usage local record = core.factions.records['example_recordid'] +-- @usage local record = core.factions.records[1] + --- -- Faction data record -- @type FactionRecord diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 60f3e79628..32b073783b 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -718,7 +718,13 @@ -- @type Creature -- @extends #Actor -- @field #Actor baseType @{#Actor} --- @field #list<#CreatureRecord> records A read-only list of all @{#CreatureRecord}s in the world database. + +--- +-- A read-only list of all @{#CreatureRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #CreatureRecord. +-- @field [parent=#Creature] #list<#CreatureRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] --- -- Whether the object is a creature. @@ -772,7 +778,13 @@ -- @extends #Actor -- @field #Actor baseType @{#Actor} -- @field [parent=#NPC] #NpcStats stats --- @field #list<#NpcRecord> records A read-only list of all @{#NpcRecord}s in the world database. + +--- +-- A read-only list of all @{#NpcRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #NpcRecord. +-- @field [parent=#NPC] #map<#NpcRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] --- -- Whether the object is an NPC or a Player. @@ -925,8 +937,11 @@ -- @field [parent=#NPC] #Classes classes --- --- A read-only list of all @{#ClassRecord}s in the world database. +-- A read-only list of all @{#ClassRecord}s in the world database, may be indexed by recordId. +-- Implements [iterables#List](iterables.html#List) of #ClassRecord. -- @field [parent=#Classes] #list<#ClassRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] --- -- Returns a read-only @{#ClassRecord} @@ -963,7 +978,10 @@ --- -- A read-only list of all @{#RaceRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #RaceRecord. -- @field [parent=#Races] #list<#RaceRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] --- -- Returns a read-only @{#RaceRecord} @@ -1120,7 +1138,10 @@ --- -- A read-only list of all @{#BirthSignRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #BirthSignRecord. -- @field [parent=#BirthSigns] #list<#BirthSignRecord> records +-- @usage local record = types.NPC.classes['example_recordid'] +-- @usage local record = types.NPC.classes[1] --- -- Returns a read-only @{#BirthSignRecord} @@ -1152,7 +1173,6 @@ -- @type Armor -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#ArmorRecord> records A read-only list of all @{#ArmorRecord}s in the world database. --- -- Whether the object is an Armor. @@ -1174,6 +1194,13 @@ -- @field #number LBracer -- @field #number RBracer +--- +-- A read-only list of all @{#ArmorRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ArmorRecord. +-- @field [parent=#Armor] #list<#ArmorRecord> records +-- @usage local record = types.Armor.records['example_recordid'] +-- @usage local record = types.Armor.records[1] + --- @{#ArmorTYPE} -- @field [parent=#Armor] #ArmorTYPE TYPE @@ -1219,7 +1246,13 @@ -- @type Book -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#BookRecord> records A read-only list of all @{#BookRecord}s in the world database. + +--- +-- A read-only list of all @{#BookRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #BookRecord. +-- @field [parent=#Book] #list<#BookRecord> records +-- @usage local record = types.Book.records['example_recordid'] +-- @usage local record = types.Book.records[1] --- -- Whether the object is a Book. @@ -1295,7 +1328,13 @@ -- @type Clothing -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#ClothingRecord> records A read-only list of all @{#ClothingRecord}s in the world database. + +--- +-- A read-only list of all @{#ClothingRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ClothingRecord. +-- @field [parent=#Clothing] #list<#ClothingRecord> records +-- @usage local record = types.Clothing.records['example_recordid'] +-- @usage local record = types.Clothing.records[1] --- -- Whether the object is a Clothing. @@ -1361,7 +1400,13 @@ -- @type Ingredient -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#IngredientRecord> records A read-only list of all @{#IngredientRecord}s in the world database. + +--- +-- A read-only list of all @{#IngredientRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #IngredientRecord. +-- @field [parent=#Ingredient] #list<#IngredientRecord> records +-- @usage local record = types.Ingredient.records['example_recordid'] +-- @usage local record = types.Ingredient.records[1] --- -- Whether the object is an Ingredient. @@ -1448,7 +1493,13 @@ -- @type Light -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#LightRecord> records A read-only list of all @{#LightRecord}s in the world database. + +--- +-- A read-only list of all @{#LightRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #LightRecord. +-- @field [parent=#Light] #list<#LightRecord> records +-- @usage local record = types.Light.records['example_recordid'] +-- @usage local record = types.Light.records[1] --- -- Whether the object is a Light. @@ -1486,7 +1537,13 @@ -- @type Miscellaneous -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#MiscellaneousRecord> records A read-only list of all @{#MiscellaneousRecord}s in the world database. + +--- +-- A read-only list of all @{#MiscellaneousRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #MiscellaneousRecord. +-- @field [parent=#Miscellaneous] #list<#MiscellaneousRecord> records +-- @usage local record = types.Miscellaneous.records['example_recordid'] +-- @usage local record = types.Miscellaneous.records[1] --- -- Whether the object is a Miscellaneous. @@ -1537,7 +1594,13 @@ -- @type Potion -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#PotionRecord> records A read-only list of all @{#PotionRecord}s in the world database. + +--- +-- A read-only list of all @{#PotionRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #PotionRecord. +-- @field [parent=#Potion] #list<#PotionRecord> records +-- @usage local record = types.Potion.records['example_recordid'] +-- @usage local record = types.Potion.records[1] --- -- Whether the object is a Potion. @@ -1578,7 +1641,13 @@ -- @type Weapon -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#WeaponRecord> records A read-only list of all @{#WeaponRecord}s in the world database. + +--- +-- A read-only list of all @{#WeaponRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #WeaponRecord. +-- @field [parent=#Weapon] #list<#WeaponRecord> records +-- @usage local record = types.Weapon.records['example_recordid'] +-- @usage local record = types.Weapon.records[1] --- -- Whether the object is a Weapon. @@ -1650,7 +1719,14 @@ -- @type Apparatus -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#ApparatusRecord> records A read-only list of all @{#ApparatusRecord}s in the world database. + + +--- +-- A read-only list of all @{#ApparatusRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ApparatusRecord. +-- @field [parent=#Apparatus] #list<#ApparatusRecord> records +-- @usage local record = types.Apparatus.records['example_recordid'] +-- @usage local record = types.Apparatus.records[1] --- -- Whether the object is an Apparatus. @@ -1693,7 +1769,13 @@ -- @type Lockpick -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#LockpickRecord> records A read-only list of all @{#LockpickRecord}s in the world database. + +--- +-- A read-only list of all @{#LockpickRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #LockpickRecord. +-- @field [parent=#Lockpick] #list<#LockpickRecord> records +-- @usage local record = types.Lockpick.records['example_recordid'] +-- @usage local record = types.Lockpick.records[1] --- -- Whether the object is a Lockpick. @@ -1726,7 +1808,13 @@ -- @type Probe -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#ProbeRecord> records A read-only list of all @{#ProbeRecord}s in the world database. + +--- +-- A read-only list of all @{#ProbeRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ProbeRecord. +-- @field [parent=#Probe] #list<#ProbeRecord> records +-- @usage local record = types.Probe.records['example_recordid'] +-- @usage local record = types.Probe.records[1] --- -- Whether the object is a Probe. @@ -1759,7 +1847,13 @@ -- @type Repair -- @extends #Item -- @field #Item baseType @{#Item} --- @field #list<#RepairRecord> records A read-only list of all @{#RepairRecord}s in the world database. + +--- +-- A read-only list of all @{#RepairRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #RepairRecord. +-- @field [parent=#Repair] #list<#RepairRecord> records +-- @usage local record = types.Repair.records['example_recordid'] +-- @usage local record = types.Repair.records[1] --- -- Whether the object is a Repair. @@ -1790,7 +1884,13 @@ --- -- @type Activator --- @field #list<#ActivatorRecord> records A read-only list of all @{#ActivatorRecord}s in the world database. + +--- +-- A read-only list of all @{#ActivatorRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ActivatorRecord. +-- @field [parent=#Activator] #list<#ActivatorRecord> records +-- @usage local record = types.Activator.records['example_recordid'] +-- @usage local record = types.Activator.records[1] --- -- Whether the object is an Activator. @@ -1827,7 +1927,13 @@ -- @type Container -- @extends #Lockable -- @field #Lockable baseType @{#Lockable} --- @field #list<#ContainerRecord> records A read-only list of all @{#ContainerRecord}s in the world database. + +--- +-- A read-only list of all @{#ContainerRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ContainerRecord. +-- @field [parent=#Container] #list<#ContainerRecord> records +-- @usage local record = types.Container.records['example_recordid'] +-- @usage local record = types.Container.records[1] --- -- Container content. @@ -1882,7 +1988,13 @@ -- @type Door -- @extends #Lockable -- @field #Lockable baseType @{#Lockable} --- @field #list<#DoorRecord> records A read-only list of all @{#DoorRecord}s in the world database. + +--- +-- A read-only list of all @{#DoorRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #DoorRecord. +-- @field [parent=#Door] #list<#DoorRecord> records +-- @usage local record = types.Door.records['example_recordid'] +-- @usage local record = types.Door.records[1] --- -- Whether the object is a Door. @@ -1936,7 +2048,13 @@ --- -- @type Static --- @field #list<#StaticRecord> records A read-only list of all @{#StaticRecord}s in the world database. + +--- +-- A read-only list of all @{#StaticRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #StaticRecord. +-- @field [parent=#Static] #list<#StaticRecord> records +-- @usage local record = types.Static.records['example_recordid'] +-- @usage local record = types.Static.records[1] --- -- Whether the object is a Static. @@ -1961,7 +2079,13 @@ --- -- @type CreatureLevelledList --- @field #list<#CreatureLevelledListRecord> records A read-only list of all @{#CreatureLevelledListRecord}s in the world database. + +--- +-- A read-only list of all @{#CreatureLevelledListRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #CreatureLevelledListRecord. +-- @field [parent=#CreatureLevelledList] #list<#CreatureLevelledListRecord> records +-- @usage local record = types.CreatureLevelledList.records['example_recordid'] +-- @usage local record = types.CreatureLevelledList.records[1] --- -- Whether the object is a CreatureLevelledList. @@ -2045,7 +2169,13 @@ --- -- @type ESM4Terminal --- @field #list<#ESM4TerminalRecord> records A read-only list of all @{#ESM4TerminalRecord}s in the world database. + +--- +-- A read-only list of all @{#ESM4TerminalRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ESM4TerminalRecord. +-- @field [parent=#ESM4Terminal] #list<#ESM4TerminalRecord> records +-- @usage local record = types.ESM4Terminal.records['example_recordid'] +-- @usage local record = types.ESM4Terminal.records[1] --- -- Whether the object is a ESM4Terminal. @@ -2110,9 +2240,11 @@ -- @return #ESM4DoorRecord --- --- Returns a read-only list of all @{#ESM4DoorRecord}s in the world database. --- @function [parent=#ESM4Door] records --- @return #list<#ESM4DoorRecord> +-- A read-only list of all @{#ESM4DoorRecord}s in the world database. +-- Implements [iterables#List](iterables.html#List) of #ESM4DoorRecord. +-- @field [parent=#ESM4Door] #list<#ESM4DoorRecord> records +-- @usage local record = types.ESM4Door.records['example_recordid'] +-- @usage local record = types.ESM4Door.records[1] --- -- @type ESM4DoorRecord diff --git a/scripts/data/integration_tests/test_lua_api/test.lua b/scripts/data/integration_tests/test_lua_api/test.lua index 53262dd168..863cdd0f57 100644 --- a/scripts/data/integration_tests/test_lua_api/test.lua +++ b/scripts/data/integration_tests/test_lua_api/test.lua @@ -2,6 +2,7 @@ local testing = require('testing_util') local core = require('openmw.core') local async = require('openmw.async') local util = require('openmw.util') +local types = require('openmw.types') local world = require('openmw.world') local function testTimers() @@ -87,6 +88,45 @@ local function testMWScript() testing.expectEqual(variableStoreCount, indexCheck) end +local function testRecordStore(store,storeName,skipPairs) + testing.expect(store.records) + local firstRecord = store.records[1] + if not firstRecord then return end + testing.expectEqual(firstRecord.id,store.records[firstRecord.id].id) + local status, _ = pcall(function() + for index, value in ipairs(store.records) do + if value.id == firstRecord.id then + testing.expectEqual(index,1,storeName) + break + end + end + end) + + testing.expectEqual(status,true,storeName) + +end + +local function testRecordStores() + for key, type in pairs(types) do + if type.records then + testRecordStore(type,key) + end + end + testRecordStore(core.magic.enchantments,"enchantments") + testRecordStore(core.magic.effects,"effects",true) + testRecordStore(core.magic.spells,"spells") + + testRecordStore(core.stats.Attribute,"Attribute") + testRecordStore(core.stats.Skill,"Skill") + + testRecordStore(core.sound,"sound") + testRecordStore(core.factions,"factions") + + testRecordStore(types.NPC.classes,"classes") + testRecordStore(types.NPC.races,"races") + testRecordStore(types.Player.birthSigns,"birthSigns") +end + local function initPlayer() player:teleport('', util.vector3(4096, 4096, 867.237), util.transform.identity) coroutine.yield() @@ -124,6 +164,7 @@ tests = { end}, {'teleport', testTeleport}, {'getGMST', testGetGMST}, + {'recordStores', testRecordStores}, {'mwscript', testMWScript}, } From 320d8ef01441c2a9f3e7cc2a93644f5206fcac28 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 25 Mar 2024 13:50:23 +0000 Subject: [PATCH 307/451] Spellcast related Lua API + spellcasting/activespell refactor --- apps/esmtool/record.cpp | 27 +- apps/opencs/model/tools/enchantmentcheck.cpp | 20 +- .../model/world/nestedcoladapterimp.hpp | 39 +-- apps/openmw/mwbase/mechanicsmanager.hpp | 2 +- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwclass/door.cpp | 2 +- apps/openmw/mwgui/enchantingdialog.cpp | 2 +- apps/openmw/mwgui/hud.cpp | 2 +- apps/openmw/mwgui/quickkeysmenu.cpp | 3 +- apps/openmw/mwgui/spellcreationdialog.cpp | 13 +- apps/openmw/mwgui/spellmodel.cpp | 6 +- apps/openmw/mwgui/tooltips.cpp | 18 +- apps/openmw/mwgui/widgets.cpp | 34 +- apps/openmw/mwlua/magicbindings.cpp | 306 +++++++++++++++--- apps/openmw/mwlua/types/ingredient.cpp | 19 +- apps/openmw/mwlua/types/potion.cpp | 7 +- apps/openmw/mwmechanics/activespells.cpp | 139 +++++--- apps/openmw/mwmechanics/activespells.hpp | 25 +- apps/openmw/mwmechanics/actors.cpp | 4 +- apps/openmw/mwmechanics/actors.hpp | 2 +- apps/openmw/mwmechanics/aicast.cpp | 8 +- apps/openmw/mwmechanics/aicast.hpp | 4 +- apps/openmw/mwmechanics/aicombat.cpp | 2 +- apps/openmw/mwmechanics/aicombataction.cpp | 12 +- apps/openmw/mwmechanics/alchemy.cpp | 12 +- apps/openmw/mwmechanics/autocalcspell.cpp | 20 +- apps/openmw/mwmechanics/character.cpp | 30 +- apps/openmw/mwmechanics/character.hpp | 4 +- apps/openmw/mwmechanics/enchanting.cpp | 22 +- .../mwmechanics/mechanicsmanagerimp.cpp | 10 +- .../mwmechanics/mechanicsmanagerimp.hpp | 2 +- apps/openmw/mwmechanics/spellcasting.cpp | 168 ++++------ apps/openmw/mwmechanics/spellcasting.hpp | 10 +- apps/openmw/mwmechanics/spelleffects.cpp | 47 ++- apps/openmw/mwmechanics/spellpriority.cpp | 24 +- apps/openmw/mwmechanics/spells.cpp | 2 +- apps/openmw/mwmechanics/spellutil.cpp | 77 ++++- apps/openmw/mwmechanics/spellutil.hpp | 6 + apps/openmw/mwrender/animation.cpp | 7 +- apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwscript/statsextensions.cpp | 2 +- apps/openmw/mwworld/class.cpp | 2 +- apps/openmw/mwworld/esmstore.cpp | 27 +- apps/openmw/mwworld/esmstore.hpp | 5 +- apps/openmw/mwworld/magiceffects.cpp | 51 ++- apps/openmw/mwworld/projectilemanager.cpp | 15 +- apps/openmw/mwworld/worldimp.cpp | 18 +- apps/openmw_test_suite/esm3/testsaveload.cpp | 37 +-- components/esm3/activespells.cpp | 39 ++- components/esm3/activespells.hpp | 44 ++- components/esm3/effectlist.cpp | 27 +- components/esm3/effectlist.hpp | 13 +- components/esm3/formatversion.hpp | 3 +- files/lua_api/openmw/core.lua | 27 +- files/lua_api/openmw/types.lua | 50 ++- 55 files changed, 974 insertions(+), 527 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 912ad0d683..fd51560f80 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -180,22 +180,23 @@ namespace void printEffectList(const ESM::EffectList& effects) { int i = 0; - for (const ESM::ENAMstruct& effect : effects.mList) + for (const ESM::IndexedENAMstruct& effect : effects.mList) { - std::cout << " Effect[" << i << "]: " << magicEffectLabel(effect.mEffectID) << " (" << effect.mEffectID - << ")" << std::endl; - if (effect.mSkill != -1) - std::cout << " Skill: " << skillLabel(effect.mSkill) << " (" << (int)effect.mSkill << ")" + std::cout << " Effect[" << i << "]: " << magicEffectLabel(effect.mData.mEffectID) << " (" + << effect.mData.mEffectID << ")" << std::endl; + if (effect.mData.mSkill != -1) + std::cout << " Skill: " << skillLabel(effect.mData.mSkill) << " (" << (int)effect.mData.mSkill << ")" << std::endl; - if (effect.mAttribute != -1) - std::cout << " Attribute: " << attributeLabel(effect.mAttribute) << " (" << (int)effect.mAttribute - << ")" << std::endl; - std::cout << " Range: " << rangeTypeLabel(effect.mRange) << " (" << effect.mRange << ")" << std::endl; + if (effect.mData.mAttribute != -1) + std::cout << " Attribute: " << attributeLabel(effect.mData.mAttribute) << " (" + << (int)effect.mData.mAttribute << ")" << std::endl; + std::cout << " Range: " << rangeTypeLabel(effect.mData.mRange) << " (" << effect.mData.mRange << ")" + << std::endl; // Area is always zero if range type is "Self" - if (effect.mRange != ESM::RT_Self) - std::cout << " Area: " << effect.mArea << std::endl; - std::cout << " Duration: " << effect.mDuration << std::endl; - std::cout << " Magnitude: " << effect.mMagnMin << "-" << effect.mMagnMax << std::endl; + if (effect.mData.mRange != ESM::RT_Self) + std::cout << " Area: " << effect.mData.mArea << std::endl; + std::cout << " Duration: " << effect.mData.mDuration << std::endl; + std::cout << " Magnitude: " << effect.mData.mMagnMin << "-" << effect.mData.mMagnMax << std::endl; i++; } } diff --git a/apps/opencs/model/tools/enchantmentcheck.cpp b/apps/opencs/model/tools/enchantmentcheck.cpp index d6cb22b738..48cee579be 100644 --- a/apps/opencs/model/tools/enchantmentcheck.cpp +++ b/apps/opencs/model/tools/enchantmentcheck.cpp @@ -60,38 +60,38 @@ void CSMTools::EnchantmentCheckStage::perform(int stage, CSMDoc::Messages& messa } else { - std::vector::const_iterator effect = enchantment.mEffects.mList.begin(); + std::vector::const_iterator effect = enchantment.mEffects.mList.begin(); for (size_t i = 1; i <= enchantment.mEffects.mList.size(); i++) { const std::string number = std::to_string(i); // At the time of writing this effects, attributes and skills are hardcoded - if (effect->mEffectID < 0 || effect->mEffectID > 142) + if (effect->mData.mEffectID < 0 || effect->mData.mEffectID > 142) { messages.add(id, "Effect #" + number + " is invalid", "", CSMDoc::Message::Severity_Error); ++effect; continue; } - if (effect->mSkill < -1 || effect->mSkill > 26) + if (effect->mData.mSkill < -1 || effect->mData.mSkill > 26) messages.add( id, "Effect #" + number + " affected skill is invalid", "", CSMDoc::Message::Severity_Error); - if (effect->mAttribute < -1 || effect->mAttribute > 7) + if (effect->mData.mAttribute < -1 || effect->mData.mAttribute > 7) messages.add( id, "Effect #" + number + " affected attribute is invalid", "", CSMDoc::Message::Severity_Error); - if (effect->mRange < 0 || effect->mRange > 2) + if (effect->mData.mRange < 0 || effect->mData.mRange > 2) messages.add(id, "Effect #" + number + " range is invalid", "", CSMDoc::Message::Severity_Error); - if (effect->mArea < 0) + if (effect->mData.mArea < 0) messages.add(id, "Effect #" + number + " area is negative", "", CSMDoc::Message::Severity_Error); - if (effect->mDuration < 0) + if (effect->mData.mDuration < 0) messages.add(id, "Effect #" + number + " duration is negative", "", CSMDoc::Message::Severity_Error); - if (effect->mMagnMin < 0) + if (effect->mData.mMagnMin < 0) messages.add( id, "Effect #" + number + " minimum magnitude is negative", "", CSMDoc::Message::Severity_Error); - if (effect->mMagnMax < 0) + if (effect->mData.mMagnMax < 0) messages.add( id, "Effect #" + number + " maximum magnitude is negative", "", CSMDoc::Message::Severity_Error); - if (effect->mMagnMin > effect->mMagnMax) + if (effect->mData.mMagnMin > effect->mData.mMagnMax) messages.add(id, "Effect #" + number + " minimum magnitude is higher than maximum magnitude", "", CSMDoc::Message::Severity_Error); ++effect; diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index 46928973fe..e18cda9611 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -255,20 +255,22 @@ namespace CSMWorld { ESXRecordT magic = record.get(); - std::vector& effectsList = magic.mEffects.mList; + std::vector& effectsList = magic.mEffects.mList; // blank row - ESM::ENAMstruct effect; - effect.mEffectID = 0; - effect.mSkill = -1; - effect.mAttribute = -1; - effect.mRange = 0; - effect.mArea = 0; - effect.mDuration = 0; - effect.mMagnMin = 0; - effect.mMagnMax = 0; + ESM::IndexedENAMstruct effect; + effect.mIndex = position; + effect.mData.mEffectID = 0; + effect.mData.mSkill = -1; + effect.mData.mAttribute = -1; + effect.mData.mRange = 0; + effect.mData.mArea = 0; + effect.mData.mDuration = 0; + effect.mData.mMagnMin = 0; + effect.mData.mMagnMax = 0; effectsList.insert(effectsList.begin() + position, effect); + magic.mEffects.updateIndexes(); record.setModified(magic); } @@ -277,12 +279,13 @@ namespace CSMWorld { ESXRecordT magic = record.get(); - std::vector& effectsList = magic.mEffects.mList; + std::vector& effectsList = magic.mEffects.mList; if (rowToRemove < 0 || rowToRemove >= static_cast(effectsList.size())) throw std::runtime_error("index out of range"); effectsList.erase(effectsList.begin() + rowToRemove); + magic.mEffects.updateIndexes(); record.setModified(magic); } @@ -292,7 +295,7 @@ namespace CSMWorld ESXRecordT magic = record.get(); magic.mEffects.mList - = static_cast>&>(nestedTable).mNestedTable; + = static_cast>&>(nestedTable).mNestedTable; record.setModified(magic); } @@ -300,19 +303,19 @@ namespace CSMWorld NestedTableWrapperBase* table(const Record& record) const override { // deleted by dtor of NestedTableStoring - return new NestedTableWrapper>(record.get().mEffects.mList); + return new NestedTableWrapper>(record.get().mEffects.mList); } QVariant getData(const Record& record, int subRowIndex, int subColIndex) const override { ESXRecordT magic = record.get(); - std::vector& effectsList = magic.mEffects.mList; + std::vector& effectsList = magic.mEffects.mList; if (subRowIndex < 0 || subRowIndex >= static_cast(effectsList.size())) throw std::runtime_error("index out of range"); - ESM::ENAMstruct effect = effectsList[subRowIndex]; + ESM::ENAMstruct effect = effectsList[subRowIndex].mData; switch (subColIndex) { case 0: @@ -374,12 +377,12 @@ namespace CSMWorld { ESXRecordT magic = record.get(); - std::vector& effectsList = magic.mEffects.mList; + std::vector& effectsList = magic.mEffects.mList; if (subRowIndex < 0 || subRowIndex >= static_cast(effectsList.size())) throw std::runtime_error("index out of range"); - ESM::ENAMstruct effect = effectsList[subRowIndex]; + ESM::ENAMstruct effect = effectsList[subRowIndex].mData; switch (subColIndex) { case 0: @@ -438,7 +441,7 @@ namespace CSMWorld throw std::runtime_error("Magic Effects subcolumn index out of range"); } - magic.mEffects.mList[subRowIndex] = effect; + magic.mEffects.mList[subRowIndex].mData = effect; record.setModified(magic); } diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index c8e353acc9..37586ed33a 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -265,7 +265,7 @@ namespace MWBase virtual bool isReadyToBlock(const MWWorld::Ptr& ptr) const = 0; virtual bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const = 0; - virtual void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell) = 0; + virtual void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell) = 0; virtual void processChangedSettings(const std::set>& settings) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index fe8b5cc13a..85e83b7f8a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -463,7 +463,7 @@ namespace MWBase */ virtual MWWorld::SpellCastState startSpellCast(const MWWorld::Ptr& actor) = 0; - virtual void castSpell(const MWWorld::Ptr& actor, bool manualSpell = false) = 0; + virtual void castSpell(const MWWorld::Ptr& actor, bool scriptedSpell = false) = 0; virtual void launchMagicBolt(const ESM::RefId& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, ESM::RefNum item) diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 7509fbf71f..c5cdc60cec 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -136,7 +136,7 @@ namespace MWClass const ESM::MagicEffect* effect = store.get().find(ESM::MagicEffect::Telekinesis); animation->addSpellCastGlow( - effect, 1); // 1 second glow to match the time taken for a door opening or closing + effect->getColor(), 1); // 1 second glow to match the time taken for a door opening or closing } } diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 8264dd60b6..af4a3e8ce3 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -273,7 +273,7 @@ namespace MWGui void EnchantingDialog::notifyEffectsChanged() { - mEffectList.mList = mEffects; + mEffectList.populate(mEffects); mEnchanting.setEffect(mEffectList); updateLabels(); } diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 1c8aad5447..0ee341c5c2 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -427,7 +427,7 @@ namespace MWGui { // use the icon of the first effect const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( - spell->mEffects.mList.front().mEffectID); + spell->mEffects.mList.front().mData.mEffectID); std::string icon = effect->mIcon; std::replace(icon.begin(), icon.end(), '/', '\\'); size_t slashPos = icon.rfind('\\'); diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 204fa00492..df7236242f 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -299,7 +299,8 @@ namespace MWGui mSelected->button->setUserString("Spell", spellId.serialize()); // use the icon of the first effect - const ESM::MagicEffect* effect = esmStore.get().find(spell->mEffects.mList.front().mEffectID); + const ESM::MagicEffect* effect + = esmStore.get().find(spell->mEffects.mList.front().mData.mEffectID); std::string path = effect->mIcon; std::replace(path.begin(), path.end(), '/', '\\'); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index d668db1dec..d8302df87c 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -470,9 +470,7 @@ namespace MWGui y *= 1.5; } - ESM::EffectList effectList; - effectList.mList = mEffects; - mSpell.mEffects = std::move(effectList); + mSpell.mEffects.populate(mEffects); mSpell.mData.mCost = int(y); mSpell.mData.mType = ESM::Spell::ST_Spell; mSpell.mData.mFlags = 0; @@ -528,10 +526,11 @@ namespace MWGui if (spell->mData.mType != ESM::Spell::ST_Spell) continue; - for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& effectInfo : spell->mEffects.mList) { + int16_t effectId = effectInfo.mData.mEffectID; const ESM::MagicEffect* effect - = MWBase::Environment::get().getESMStore()->get().find(effectInfo.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(effectId); // skip effects that do not allow spellmaking/enchanting int requiredFlags @@ -539,8 +538,8 @@ namespace MWGui if (!(effect->mData.mFlags & requiredFlags)) continue; - if (std::find(knownEffects.begin(), knownEffects.end(), effectInfo.mEffectID) == knownEffects.end()) - knownEffects.push_back(effectInfo.mEffectID); + if (std::find(knownEffects.begin(), knownEffects.end(), effectId) == knownEffects.end()) + knownEffects.push_back(effectId); } } diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 385464da24..3d70c391c9 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -48,14 +48,14 @@ namespace MWGui for (const auto& effect : effects.mList) { - short effectId = effect.mEffectID; + short effectId = effect.mData.mEffectID; if (effectId != -1) { const ESM::MagicEffect* magicEffect = store.get().find(effectId); const ESM::Attribute* attribute - = store.get().search(ESM::Attribute::indexToRefId(effect.mAttribute)); - const ESM::Skill* skill = store.get().search(ESM::Skill::indexToRefId(effect.mSkill)); + = store.get().search(ESM::Attribute::indexToRefId(effect.mData.mAttribute)); + const ESM::Skill* skill = store.get().search(ESM::Skill::indexToRefId(effect.mData.mSkill)); std::string fullEffectName = MWMechanics::getMagicEffectString(*magicEffect, attribute, skill); std::string convert = Utf8Stream::lowerCaseUtf8(fullEffectName); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 0e0c56c194..960a4a5a21 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -222,17 +222,17 @@ namespace MWGui = store->get().find(ESM::RefId::deserialize(focus->getUserString("Spell"))); info.caption = spell->mName; Widgets::SpellEffectList effects; - for (const ESM::ENAMstruct& spellEffect : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& spellEffect : spell->mEffects.mList) { Widgets::SpellEffectParams params; - params.mEffectID = spellEffect.mEffectID; - params.mSkill = ESM::Skill::indexToRefId(spellEffect.mSkill); - params.mAttribute = ESM::Attribute::indexToRefId(spellEffect.mAttribute); - params.mDuration = spellEffect.mDuration; - params.mMagnMin = spellEffect.mMagnMin; - params.mMagnMax = spellEffect.mMagnMax; - params.mRange = spellEffect.mRange; - params.mArea = spellEffect.mArea; + params.mEffectID = spellEffect.mData.mEffectID; + params.mSkill = ESM::Skill::indexToRefId(spellEffect.mData.mSkill); + params.mAttribute = ESM::Attribute::indexToRefId(spellEffect.mData.mAttribute); + params.mDuration = spellEffect.mData.mDuration; + params.mMagnMin = spellEffect.mData.mMagnMin; + params.mMagnMax = spellEffect.mData.mMagnMax; + params.mRange = spellEffect.mData.mRange; + params.mArea = spellEffect.mData.mArea; params.mIsConstant = (spell->mData.mType == ESM::Spell::ST_Ability); params.mNoTarget = false; effects.push_back(params); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index fea6d490c5..6cc5bdfdf5 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -195,18 +195,18 @@ namespace MWGui::Widgets const ESM::Spell* spell = store.get().search(mId); MYGUI_ASSERT(spell, "spell with id '" << mId << "' not found"); - for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& effectInfo : spell->mEffects.mList) { MWSpellEffectPtr effect = creator->createWidget("MW_EffectImage", coord, MyGUI::Align::Default); SpellEffectParams params; - params.mEffectID = effectInfo.mEffectID; - params.mSkill = ESM::Skill::indexToRefId(effectInfo.mSkill); - params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mAttribute); - params.mDuration = effectInfo.mDuration; - params.mMagnMin = effectInfo.mMagnMin; - params.mMagnMax = effectInfo.mMagnMax; - params.mRange = effectInfo.mRange; + params.mEffectID = effectInfo.mData.mEffectID; + params.mSkill = ESM::Skill::indexToRefId(effectInfo.mData.mSkill); + params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mData.mAttribute); + params.mDuration = effectInfo.mData.mDuration; + params.mMagnMin = effectInfo.mData.mMagnMin; + params.mMagnMax = effectInfo.mData.mMagnMax; + params.mRange = effectInfo.mData.mRange; params.mIsConstant = (flags & MWEffectList::EF_Constant) != 0; params.mNoTarget = (flags & MWEffectList::EF_NoTarget); params.mNoMagnitude = (flags & MWEffectList::EF_NoMagnitude); @@ -308,17 +308,17 @@ namespace MWGui::Widgets SpellEffectList MWEffectList::effectListFromESM(const ESM::EffectList* effects) { SpellEffectList result; - for (const ESM::ENAMstruct& effectInfo : effects->mList) + for (const ESM::IndexedENAMstruct& effectInfo : effects->mList) { SpellEffectParams params; - params.mEffectID = effectInfo.mEffectID; - params.mSkill = ESM::Skill::indexToRefId(effectInfo.mSkill); - params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mAttribute); - params.mDuration = effectInfo.mDuration; - params.mMagnMin = effectInfo.mMagnMin; - params.mMagnMax = effectInfo.mMagnMax; - params.mRange = effectInfo.mRange; - params.mArea = effectInfo.mArea; + params.mEffectID = effectInfo.mData.mEffectID; + params.mSkill = ESM::Skill::indexToRefId(effectInfo.mData.mSkill); + params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mData.mAttribute); + params.mDuration = effectInfo.mData.mDuration; + params.mMagnMin = effectInfo.mData.mMagnMin; + params.mMagnMax = effectInfo.mData.mMagnMax; + params.mRange = effectInfo.mData.mRange; + params.mArea = effectInfo.mData.mArea; result.push_back(params); } return result; diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 1e3cb2ab69..0e2d5217ae 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -13,12 +13,14 @@ #include #include #include +#include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwmechanics/activespells.hpp" +#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/magiceffects.hpp" #include "../mwmechanics/spellutil.hpp" @@ -144,7 +146,7 @@ namespace sol { }; template <> - struct is_automagical : std::false_type + struct is_automagical : std::false_type { }; template <> @@ -192,6 +194,26 @@ namespace MWLua return ESM::RefId::deserializeText(LuaUtil::cast(recordOrId)); } + static const ESM::Spell* toSpell(const sol::object& spellOrId) + { + if (spellOrId.is()) + return spellOrId.as(); + else + { + auto& store = MWBase::Environment::get().getWorld()->getStore(); + auto refId = ESM::RefId::deserializeText(LuaUtil::cast(spellOrId)); + return store.get().find(refId); + } + } + + static sol::table effectParamsListToTable(sol::state_view& lua, const std::vector& effects) + { + sol::table res(lua, sol::create); + for (size_t i = 0; i < effects.size(); ++i) + res[i + 1] = effects[i]; // ESM::IndexedENAMstruct (effect params) + return res; + } + sol::table initCoreMagicBindings(const Context& context) { sol::state_view& lua = context.mLua->sol(); @@ -314,12 +336,12 @@ namespace MWLua spellT["name"] = sol::readonly_property([](const ESM::Spell& rec) -> std::string_view { return rec.mName; }); spellT["type"] = sol::readonly_property([](const ESM::Spell& rec) -> int { return rec.mData.mType; }); spellT["cost"] = sol::readonly_property([](const ESM::Spell& rec) -> int { return rec.mData.mCost; }); - spellT["effects"] = sol::readonly_property([&lua](const ESM::Spell& rec) -> sol::table { - sol::table res(lua, sol::create); - for (size_t i = 0; i < rec.mEffects.mList.size(); ++i) - res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params) - return res; - }); + spellT["alwaysSucceedFlag"] = sol::readonly_property( + [](const ESM::Spell& rec) -> bool { return !!(rec.mData.mFlags & ESM::Spell::F_Always); }); + spellT["autocalcFlag"] = sol::readonly_property( + [](const ESM::Spell& rec) -> bool { return !!(rec.mData.mFlags & ESM::Spell::F_Autocalc); }); + spellT["effects"] = sol::readonly_property( + [&lua](const ESM::Spell& rec) -> sol::table { return effectParamsListToTable(lua, rec.mEffects.mList); }); // Enchantment record auto enchantT = lua.new_usertype("ESM3_Enchantment"); @@ -334,46 +356,49 @@ namespace MWLua enchantT["charge"] = sol::readonly_property([](const ESM::Enchantment& rec) -> int { return rec.mData.mCharge; }); enchantT["effects"] = sol::readonly_property([&lua](const ESM::Enchantment& rec) -> sol::table { - sol::table res(lua, sol::create); - for (size_t i = 0; i < rec.mEffects.mList.size(); ++i) - res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params) - return res; + return effectParamsListToTable(lua, rec.mEffects.mList); }); // Effect params - auto effectParamsT = lua.new_usertype("ESM3_EffectParams"); - effectParamsT[sol::meta_function::to_string] = [magicEffectStore](const ESM::ENAMstruct& params) { - const ESM::MagicEffect* const rec = magicEffectStore->find(params.mEffectID); + auto effectParamsT = lua.new_usertype("ESM3_EffectParams"); + effectParamsT[sol::meta_function::to_string] = [magicEffectStore](const ESM::IndexedENAMstruct& params) { + const ESM::MagicEffect* const rec = magicEffectStore->find(params.mData.mEffectID); return "ESM3_EffectParams[" + ESM::MagicEffect::indexToGmstString(rec->mIndex) + "]"; }; - effectParamsT["effect"] - = sol::readonly_property([magicEffectStore](const ESM::ENAMstruct& params) -> const ESM::MagicEffect* { - return magicEffectStore->find(params.mEffectID); - }); + effectParamsT["effect"] = sol::readonly_property( + [magicEffectStore](const ESM::IndexedENAMstruct& params) -> const ESM::MagicEffect* { + return magicEffectStore->find(params.mData.mEffectID); + }); + effectParamsT["id"] = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> std::string { + auto name = ESM::MagicEffect::indexToName(params.mData.mEffectID); + return Misc::StringUtils::lowerCase(name); + }); effectParamsT["affectedSkill"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> sol::optional { - ESM::RefId id = ESM::Skill::indexToRefId(params.mSkill); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> sol::optional { + ESM::RefId id = ESM::Skill::indexToRefId(params.mData.mSkill); if (!id.empty()) return id.serializeText(); return sol::nullopt; }); effectParamsT["affectedAttribute"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> sol::optional { - ESM::RefId id = ESM::Attribute::indexToRefId(params.mAttribute); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> sol::optional { + ESM::RefId id = ESM::Attribute::indexToRefId(params.mData.mAttribute); if (!id.empty()) return id.serializeText(); return sol::nullopt; }); effectParamsT["range"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mRange; }); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mRange; }); effectParamsT["area"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mArea; }); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mArea; }); effectParamsT["magnitudeMin"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mMagnMin; }); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mMagnMin; }); effectParamsT["magnitudeMax"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mMagnMax; }); - effectParamsT["duration"] - = sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mDuration; }); + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mMagnMax; }); + effectParamsT["duration"] = sol::readonly_property( + [](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mDuration; }); + effectParamsT["index"] + = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mIndex; }); // MagicEffect record auto magicEffectT = context.mLua->sol().new_usertype("ESM3_MagicEffect"); @@ -394,12 +419,22 @@ namespace MWLua magicEffectT["continuousVfx"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> bool { return (rec.mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; }); - magicEffectT["castingStatic"] = sol::readonly_property( + magicEffectT["areaSound"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mAreaSound.serializeText(); }); + magicEffectT["boltSound"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mBoltSound.serializeText(); }); + magicEffectT["castSound"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mCastSound.serializeText(); }); + magicEffectT["hitSound"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mHitSound.serializeText(); }); + magicEffectT["areaStatic"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mArea.serializeText(); }); + magicEffectT["boltStatic"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> std::string { return rec.mBolt.serializeText(); }); + magicEffectT["castStatic"] = sol::readonly_property( [](const ESM::MagicEffect& rec) -> std::string { return rec.mCasting.serializeText(); }); magicEffectT["hitStatic"] = sol::readonly_property( [](const ESM::MagicEffect& rec) -> std::string { return rec.mHit.serializeText(); }); - magicEffectT["areaStatic"] = sol::readonly_property( - [](const ESM::MagicEffect& rec) -> std::string { return rec.mArea.serializeText(); }); magicEffectT["name"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> std::string_view { return MWBase::Environment::get() .getWorld() @@ -415,8 +450,20 @@ namespace MWLua magicEffectT["color"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> Misc::Color { return Misc::Color(rec.mData.mRed / 255.f, rec.mData.mGreen / 255.f, rec.mData.mBlue / 255.f, 1.f); }); + magicEffectT["hasDuration"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return !(rec.mData.mFlags & ESM::MagicEffect::NoDuration); }); + magicEffectT["hasMagnitude"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return !(rec.mData.mFlags & ESM::MagicEffect::NoMagnitude); }); + // TODO: Not self-explanatory. Needs either a better name or documentation. The description in + // loadmgef.hpp is uninformative. + magicEffectT["isAppliedOnce"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::AppliedOnce; }); magicEffectT["harmful"] = sol::readonly_property( [](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::Harmful; }); + magicEffectT["casterLinked"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::CasterLinked; }); + magicEffectT["nonRecastable"] = sol::readonly_property( + [](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::NonRecastable; }); // TODO: Should we expose it? What happens if a spell has several effects with different projectileSpeed? // magicEffectT["projectileSpeed"] @@ -430,6 +477,8 @@ namespace MWLua auto name = ESM::MagicEffect::indexToName(effect.mEffectId); return Misc::StringUtils::lowerCase(name); }); + activeSpellEffectT["index"] + = sol::readonly_property([](const ESM::ActiveEffect& effect) -> int { return effect.mEffectIndex; }); activeSpellEffectT["name"] = sol::readonly_property([](const ESM::ActiveEffect& effect) -> std::string { return MWMechanics::EffectKey(effect.mEffectId, effect.getSkillOrAttribute()).toString(); }); @@ -493,12 +542,13 @@ namespace MWLua auto activeSpellT = context.mLua->sol().new_usertype("ActiveSpellParams"); activeSpellT[sol::meta_function::to_string] = [](const ActiveSpell& activeSpell) { - return "ActiveSpellParams[" + activeSpell.mParams.getId().serializeText() + "]"; + return "ActiveSpellParams[" + activeSpell.mParams.getSourceSpellId().serializeText() + "]"; }; activeSpellT["name"] = sol::readonly_property( [](const ActiveSpell& activeSpell) -> std::string_view { return activeSpell.mParams.getDisplayName(); }); - activeSpellT["id"] = sol::readonly_property( - [](const ActiveSpell& activeSpell) -> std::string { return activeSpell.mParams.getId().serializeText(); }); + activeSpellT["id"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> std::string { + return activeSpell.mParams.getSourceSpellId().serializeText(); + }); activeSpellT["item"] = sol::readonly_property([&lua](const ActiveSpell& activeSpell) -> sol::object { auto item = activeSpell.mParams.getItem(); if (!item.isSet()) @@ -535,6 +585,21 @@ namespace MWLua } return res; }); + activeSpellT["fromEquipment"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool { + return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_Equipment); + }); + activeSpellT["temporary"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool { + return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_Temporary); + }); + activeSpellT["affectsBaseValues"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool { + return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues); + }); + activeSpellT["stackable"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool { + return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_Stackable); + }); + activeSpellT["activeSpellId"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> std::string { + return activeSpell.mParams.getActiveSpellId().serializeText(); + }); auto activeEffectT = context.mLua->sol().new_usertype("ActiveEffect"); @@ -573,6 +638,78 @@ namespace MWLua return LuaUtil::makeReadOnly(magicApi); } + static std::pair> getNameAndMagicEffects( + const MWWorld::Ptr& actor, ESM::RefId id, const sol::table& effects, bool quiet) + { + std::vector effectIndexes; + + for (const auto& entry : effects) + { + if (entry.second.is()) + effectIndexes.push_back(entry.second.as()); + else if (entry.second.is()) + throw std::runtime_error("Error: Adding effects as enam structs is not implemented, use indexes."); + else + throw std::runtime_error("Unexpected entry in 'effects' table while trying to add to active effects"); + } + + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + + auto getEffectsFromIndexes = [&](const ESM::EffectList& effects) { + std::vector enams; + for (auto index : effectIndexes) + enams.push_back(effects.mList.at(index)); + return enams; + }; + + auto getNameAndEffects = [&](auto* record) { + return std::pair>( + record->mName, getEffectsFromIndexes(record->mEffects)); + }; + auto getNameAndEffectsEnch = [&](auto* record) { + auto* enchantment = esmStore.get().find(record->mEnchant); + return std::pair>( + record->mName, getEffectsFromIndexes(enchantment->mEffects)); + }; + switch (esmStore.find(id)) + { + case ESM::REC_ALCH: + return getNameAndEffects(esmStore.get().find(id)); + case ESM::REC_INGR: + { + // Ingredients are a special case as their effect list is calculated on consumption. + const ESM::Ingredient* ingredient = esmStore.get().find(id); + std::vector enams; + quiet = quiet || actor != MWMechanics::getPlayer(); + for (uint32_t i = 0; i < effectIndexes.size(); i++) + { + if (auto effect = MWMechanics::rollIngredientEffect(actor, ingredient, effectIndexes[i])) + enams.push_back(effect->mList[0]); + } + if (enams.empty() && !quiet) + { + // "X has no effect on you" + std::string message = esmStore.get().find("sNotifyMessage50")->mValue.getString(); + message = Misc::StringUtils::format(message, ingredient->mName); + MWBase::Environment::get().getWindowManager()->messageBox(message); + } + return { ingredient->mName, std::move(enams) }; + } + case ESM::REC_ARMO: + return getNameAndEffectsEnch(esmStore.get().find(id)); + case ESM::REC_BOOK: + return getNameAndEffectsEnch(esmStore.get().find(id)); + case ESM::REC_CLOT: + return getNameAndEffectsEnch(esmStore.get().find(id)); + case ESM::REC_WEAP: + return getNameAndEffectsEnch(esmStore.get().find(id)); + default: + // esmStore.find doesn't find REC_SPELs + case ESM::REC_SPEL: + return getNameAndEffects(esmStore.get().find(id)); + } + } + void addActorMagicBindings(sol::table& actor, const Context& context) { const MWWorld::Store* spellStore @@ -731,6 +868,16 @@ namespace MWLua }); }; + // types.Actor.spells(o):canUsePower() + spellsT["canUsePower"] = [](const ActorSpells& spells, const sol::object& spellOrId) -> bool { + if (spells.mActor.isLObject()) + throw std::runtime_error("Local scripts can modify only spells of the actor they are attached to."); + auto* spell = toSpell(spellOrId); + if (auto* store = spells.getStore()) + return store->canUsePower(spell); + return false; + }; + // pairs(types.Actor.activeSpells(o)) activeSpellsT["__pairs"] = [](sol::this_state ts, ActorActiveSpells& self) { sol::state_view lua(ts); @@ -738,7 +885,7 @@ namespace MWLua return sol::as_function([lua, self]() mutable -> std::pair { if (!self.isEnd()) { - auto id = sol::make_object(lua, self.mIterator->getId().serializeText()); + auto id = sol::make_object(lua, self.mIterator->getSourceSpellId().serializeText()); auto params = sol::make_object(lua, ActiveSpell{ self.mActor, *self.mIterator }); self.advance(); return { id, params }; @@ -762,14 +909,97 @@ namespace MWLua }; // types.Actor.activeSpells(o):remove(id) - activeSpellsT["remove"] = [](const ActorActiveSpells& spells, const sol::object& spellOrId) { + activeSpellsT["remove"] = [context](const ActorActiveSpells& spells, std::string_view idStr) { + if (spells.isLObject()) + throw std::runtime_error("Local scripts can modify effect only on the actor they are attached to."); + + context.mLuaManager->addAction([spells = spells, id = ESM::RefId::deserializeText(idStr)]() { + if (auto* store = spells.getStore()) + { + auto it = store->getActiveSpellById(id); + if (it != store->end()) + { + if (it->hasFlag(ESM::ActiveSpells::Flag_Temporary)) + store->removeEffectsByActiveSpellId(spells.mActor.ptr(), id); + else + throw std::runtime_error("Can only remove temporary effects."); + } + } + }); + }; + + // types.Actor.activeSpells(o):add(id, spellid, effects, options) + activeSpellsT["add"] = [](const ActorActiveSpells& spells, const sol::table& options) { if (spells.isLObject()) throw std::runtime_error("Local scripts can modify effect only on the actor they are attached to."); - auto id = toSpellId(spellOrId); if (auto* store = spells.getStore()) { - store->removeEffects(spells.mActor.ptr(), id); + ESM::RefId id = ESM::RefId::deserializeText(options.get("id")); + sol::optional item = options.get>("item"); + ESM::RefNum itemId; + if (item) + itemId = item->id(); + sol::optional caster = options.get>("caster"); + bool stackable = options.get_or("stackable", false); + bool ignoreReflect = options.get_or("ignoreReflect", false); + bool ignoreSpellAbsorption = options.get_or("ignoreSpellAbsorption", false); + bool ignoreResistances = options.get_or("ignoreResistances", false); + sol::table effects = options.get("effects"); + bool quiet = options.get_or("quiet", false); + if (effects.empty()) + throw std::runtime_error("Error: Parameter 'effects': cannot be an empty list/table"); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + auto [name, enams] = getNameAndMagicEffects(spells.mActor.ptr(), id, effects, quiet); + name = options.get_or("name", name); + + MWWorld::Ptr casterPtr; + if (caster) + casterPtr = caster->ptrOrEmpty(); + + bool affectsHealth = false; + MWMechanics::ActiveSpells::ActiveSpellParams params(casterPtr, id, name, itemId); + params.setFlag(ESM::ActiveSpells::Flag_Lua); + params.setFlag(ESM::ActiveSpells::Flag_Temporary); + if (stackable) + params.setFlag(ESM::ActiveSpells::Flag_Stackable); + + for (auto enam : enams) + { + const ESM::MagicEffect* mgef = esmStore.get().find(enam.mData.mEffectID); + MWMechanics::ActiveSpells::ActiveEffect effect; + effect.mEffectId = enam.mData.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; + effect.mMagnitude = 0.f; + effect.mMinMagnitude = enam.mData.mMagnMin; + effect.mMaxMagnitude = enam.mData.mMagnMax; + effect.mEffectIndex = enam.mIndex; + effect.mFlags = ESM::ActiveEffect::Flag_None; + if (ignoreReflect) + effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect; + if (ignoreSpellAbsorption) + effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; + if (ignoreResistances) + effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances; + + bool hasDuration = !(mgef->mData.mFlags & ESM::MagicEffect::NoDuration); + effect.mDuration = hasDuration ? static_cast(enam.mData.mDuration) : 1.f; + + bool appliedOnce = mgef->mData.mFlags & ESM::MagicEffect::AppliedOnce; + if (!appliedOnce) + effect.mDuration = std::max(1.f, effect.mDuration); + effect.mTimeLeft = effect.mDuration; + params.getEffects().emplace_back(effect); + + affectsHealth = affectsHealth || mgef->mData.mFlags & ESM::MagicEffect::Harmful + || effect.mEffectId == ESM::MagicEffect::RestoreHealth; + } + store->addSpell(params); + if (affectsHealth && casterPtr == MWMechanics::getPlayer()) + // If player is attempting to cast a harmful spell on or is healing a living target, show the + // target's HP bar. + // TODO: This should be moved to Lua once the HUD has been dehardcoded + MWBase::Environment::get().getWindowManager()->setEnemy(spells.mActor.ptr()); } }; diff --git a/apps/openmw/mwlua/types/ingredient.cpp b/apps/openmw/mwlua/types/ingredient.cpp index abfd2329ce..f7c3a8a050 100644 --- a/apps/openmw/mwlua/types/ingredient.cpp +++ b/apps/openmw/mwlua/types/ingredient.cpp @@ -47,15 +47,16 @@ namespace MWLua { if (rec.mData.mEffectID[i] < 0) continue; - ESM::ENAMstruct effect; - effect.mEffectID = rec.mData.mEffectID[i]; - effect.mSkill = rec.mData.mSkills[i]; - effect.mAttribute = rec.mData.mAttributes[i]; - effect.mRange = ESM::RT_Self; - effect.mArea = 0; - effect.mDuration = 0; - effect.mMagnMin = 0; - effect.mMagnMax = 0; + ESM::IndexedENAMstruct effect; + effect.mData.mEffectID = rec.mData.mEffectID[i]; + effect.mData.mSkill = rec.mData.mSkills[i]; + effect.mData.mAttribute = rec.mData.mAttributes[i]; + effect.mData.mRange = ESM::RT_Self; + effect.mData.mArea = 0; + effect.mData.mDuration = 0; + effect.mData.mMagnMin = 0; + effect.mData.mMagnMax = 0; + effect.mIndex = i; res[i + 1] = effect; } return res; diff --git a/apps/openmw/mwlua/types/potion.cpp b/apps/openmw/mwlua/types/potion.cpp index 50aca6d9e7..d686bdb1f7 100644 --- a/apps/openmw/mwlua/types/potion.cpp +++ b/apps/openmw/mwlua/types/potion.cpp @@ -46,7 +46,10 @@ namespace size_t numEffects = effectsTable.size(); potion.mEffects.mList.resize(numEffects); for (size_t i = 0; i < numEffects; ++i) - potion.mEffects.mList[i] = LuaUtil::cast(effectsTable[i + 1]); + { + potion.mEffects.mList[i] = LuaUtil::cast(effectsTable[i + 1]); + } + potion.mEffects.updateIndexes(); } return potion; } @@ -83,7 +86,7 @@ namespace MWLua record["effects"] = sol::readonly_property([context](const ESM::Potion& rec) -> sol::table { sol::table res(context.mLua->sol(), sol::create); for (size_t i = 0; i < rec.mEffects.mList.size(); ++i) - res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params) + res[i + 1] = rec.mEffects.mList[i]; // ESM::IndexedENAMstruct (effect params) return res; }); } diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 937e7a6658..2fb7df61c1 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -8,6 +8,7 @@ #include +#include #include #include #include @@ -49,16 +50,15 @@ namespace void addEffects( std::vector& effects, const ESM::EffectList& list, bool ignoreResistances = false) { - int currentEffectIndex = 0; for (const auto& enam : list.mList) { ESM::ActiveEffect effect; - effect.mEffectId = enam.mEffectID; - effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mEffectId = enam.mData.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; effect.mMagnitude = 0.f; - effect.mMinMagnitude = enam.mMagnMin; - effect.mMaxMagnitude = enam.mMagnMax; - effect.mEffectIndex = currentEffectIndex++; + effect.mMinMagnitude = enam.mData.mMagnMin; + effect.mMaxMagnitude = enam.mData.mMagnMax; + effect.mEffectIndex = enam.mIndex; effect.mFlags = ESM::ActiveEffect::Flag_None; if (ignoreResistances) effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances; @@ -82,12 +82,13 @@ namespace MWMechanics mActiveSpells.mIterating = false; } - ActiveSpells::ActiveSpellParams::ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster) - : mId(cast.mId) - , mDisplayName(cast.mSourceName) + ActiveSpells::ActiveSpellParams::ActiveSpellParams( + const MWWorld::Ptr& caster, const ESM::RefId& id, std::string_view sourceName, ESM::RefNum item) + : mSourceSpellId(id) + , mDisplayName(sourceName) , mCasterActorId(-1) - , mItem(cast.mItem) - , mType(cast.mType) + , mItem(item) + , mFlags() , mWorsenings(-1) { if (!caster.isEmpty() && caster.getClass().isActor()) @@ -96,48 +97,52 @@ namespace MWMechanics ActiveSpells::ActiveSpellParams::ActiveSpellParams( const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances) - : mId(spell->mId) + : mSourceSpellId(spell->mId) , mDisplayName(spell->mName) , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) - , mType(spell->mData.mType == ESM::Spell::ST_Ability ? ESM::ActiveSpells::Type_Ability - : ESM::ActiveSpells::Type_Permanent) + , mFlags() , mWorsenings(-1) { assert(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power); + setFlag(ESM::ActiveSpells::Flag_SpellStore); + if (spell->mData.mType == ESM::Spell::ST_Ability) + setFlag(ESM::ActiveSpells::Flag_AffectsBaseValues); addEffects(mEffects, spell->mEffects, ignoreResistances); } ActiveSpells::ActiveSpellParams::ActiveSpellParams( const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, const MWWorld::Ptr& actor) - : mId(item.getCellRef().getRefId()) + : mSourceSpellId(item.getCellRef().getRefId()) , mDisplayName(item.getClass().getName(item)) , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) , mItem(item.getCellRef().getRefNum()) - , mType(ESM::ActiveSpells::Type_Enchantment) + , mFlags() , mWorsenings(-1) { assert(enchantment->mData.mType == ESM::Enchantment::ConstantEffect); addEffects(mEffects, enchantment->mEffects); + setFlag(ESM::ActiveSpells::Flag_Equipment); } ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params) - : mId(params.mId) + : mActiveSpellId(params.mActiveSpellId) + , mSourceSpellId(params.mSourceSpellId) , mEffects(params.mEffects) , mDisplayName(params.mDisplayName) , mCasterActorId(params.mCasterActorId) , mItem(params.mItem) - , mType(params.mType) + , mFlags(params.mFlags) , mWorsenings(params.mWorsenings) , mNextWorsening({ params.mNextWorsening }) { } ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor) - : mId(params.mId) + : mSourceSpellId(params.mSourceSpellId) , mDisplayName(params.mDisplayName) , mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) , mItem(params.mItem) - , mType(params.mType) + , mFlags(params.mFlags) , mWorsenings(-1) { } @@ -145,17 +150,23 @@ namespace MWMechanics ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const { ESM::ActiveSpells::ActiveSpellParams params; - params.mId = mId; + params.mActiveSpellId = mActiveSpellId; + params.mSourceSpellId = mSourceSpellId; params.mEffects = mEffects; params.mDisplayName = mDisplayName; params.mCasterActorId = mCasterActorId; params.mItem = mItem; - params.mType = mType; + params.mFlags = mFlags; params.mWorsenings = mWorsenings; params.mNextWorsening = mNextWorsening.toEsm(); return params; } + void ActiveSpells::ActiveSpellParams::setFlag(ESM::ActiveSpells::Flags flag) + { + mFlags = static_cast(mFlags | flag); + } + void ActiveSpells::ActiveSpellParams::worsen() { ++mWorsenings; @@ -178,21 +189,31 @@ namespace MWMechanics { // Enchantment id is not stored directly. Instead the enchanted item is stored. const auto& store = MWBase::Environment::get().getESMStore(); - switch (store->find(mId)) + switch (store->find(mSourceSpellId)) { case ESM::REC_ARMO: - return store->get().find(mId)->mEnchant; + return store->get().find(mSourceSpellId)->mEnchant; case ESM::REC_BOOK: - return store->get().find(mId)->mEnchant; + return store->get().find(mSourceSpellId)->mEnchant; case ESM::REC_CLOT: - return store->get().find(mId)->mEnchant; + return store->get().find(mSourceSpellId)->mEnchant; case ESM::REC_WEAP: - return store->get().find(mId)->mEnchant; + return store->get().find(mSourceSpellId)->mEnchant; default: return {}; } } + const ESM::Spell* ActiveSpells::ActiveSpellParams::getSpell() const + { + return MWBase::Environment::get().getESMStore()->get().search(getSourceSpellId()); + } + + bool ActiveSpells::ActiveSpellParams::hasFlag(ESM::ActiveSpells::Flags flags) const + { + return static_cast(mFlags & flags) == flags; + } + void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration) { if (mIterating) @@ -203,8 +224,7 @@ namespace MWMechanics // Erase no longer active spells and effects for (auto spellIt = mSpells.begin(); spellIt != mSpells.end();) { - if (spellIt->mType != ESM::ActiveSpells::Type_Temporary - && spellIt->mType != ESM::ActiveSpells::Type_Consumable) + if (!spellIt->hasFlag(ESM::ActiveSpells::Flag_Temporary)) { ++spellIt; continue; @@ -244,7 +264,10 @@ namespace MWMechanics { if (spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power && !isSpellActive(spell->mId)) + { mSpells.emplace_back(ActiveSpellParams{ spell, ptr }); + mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); + } } bool updateSpellWindow = false; @@ -270,8 +293,8 @@ namespace MWMechanics if (std::find_if(mSpells.begin(), mSpells.end(), [&](const ActiveSpellParams& params) { return params.mItem == slot->getCellRef().getRefNum() - && params.mType == ESM::ActiveSpells::Type_Enchantment - && params.mId == slot->getCellRef().getRefId(); + && params.hasFlag(ESM::ActiveSpells::Flag_Equipment) + && params.mSourceSpellId == slot->getCellRef().getRefId(); }) != mSpells.end()) continue; @@ -279,8 +302,8 @@ namespace MWMechanics // invisibility manually purgeEffect(ptr, ESM::MagicEffect::Invisibility); applyPurges(ptr); - const ActiveSpellParams& params - = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, ptr }); + ActiveSpellParams& params = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, ptr }); + params.setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); for (const auto& effect : params.mEffects) MWMechanics::playEffects( ptr, *world->getStore().get().find(effect.mEffectId), playNonLooping); @@ -350,12 +373,11 @@ namespace MWMechanics continue; bool remove = false; - if (spellIt->mType == ESM::ActiveSpells::Type_Ability - || spellIt->mType == ESM::ActiveSpells::Type_Permanent) + if (spellIt->hasFlag(ESM::ActiveSpells::Flag_SpellStore)) { try { - remove = !spells.hasSpell(spellIt->mId); + remove = !spells.hasSpell(spellIt->mSourceSpellId); } catch (const std::runtime_error& e) { @@ -363,9 +385,9 @@ namespace MWMechanics Log(Debug::Error) << "Removing active effect: " << e.what(); } } - else if (spellIt->mType == ESM::ActiveSpells::Type_Enchantment) + else if (spellIt->hasFlag(ESM::ActiveSpells::Flag_Equipment)) { - // Remove constant effect enchantments that have been unequipped + // Remove effects tied to equipment that has been unequipped const auto& store = ptr.getClass().getInventoryStore(ptr); remove = true; for (int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++) @@ -411,11 +433,11 @@ namespace MWMechanics void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell) { - if (spell.mType != ESM::ActiveSpells::Type_Consumable) + if (!spell.hasFlag(ESM::ActiveSpells::Flag_Stackable)) { auto found = std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& existing) { - return spell.mId == existing.mId && spell.mCasterActorId == existing.mCasterActorId - && spell.mItem == existing.mItem; + return spell.mSourceSpellId == existing.mSourceSpellId + && spell.mCasterActorId == existing.mCasterActorId && spell.mItem == existing.mItem; }); if (found != mSpells.end()) { @@ -428,6 +450,7 @@ namespace MWMechanics } } mSpells.emplace_back(spell); + mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); } ActiveSpells::ActiveSpells() @@ -445,10 +468,19 @@ namespace MWMechanics return mSpells.end(); } + ActiveSpells::TIterator ActiveSpells::getActiveSpellById(const ESM::RefId& id) + { + for (TIterator it = begin(); it != end(); it++) + if (it->getActiveSpellId() == id) + return it; + return end(); + } + bool ActiveSpells::isSpellActive(const ESM::RefId& id) const { - return std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& spell) { return spell.mId == id; }) - != mSpells.end(); + return std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& spell) { + return spell.mSourceSpellId == id; + }) != mSpells.end(); } bool ActiveSpells::isEnchantmentActive(const ESM::RefId& id) const @@ -557,9 +589,14 @@ namespace MWMechanics return removedCurrentSpell; } - void ActiveSpells::removeEffects(const MWWorld::Ptr& ptr, const ESM::RefId& id) + void ActiveSpells::removeEffectsBySourceSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id) + { + purge([=](const ActiveSpellParams& params) { return params.mSourceSpellId == id; }, ptr); + } + + void ActiveSpells::removeEffectsByActiveSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id) { - purge([=](const ActiveSpellParams& params) { return params.mId == id; }, ptr); + purge([=](const ActiveSpellParams& params) { return params.mActiveSpellId == id; }, ptr); } void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, int effectId, ESM::RefId effectArg) @@ -604,19 +641,19 @@ namespace MWMechanics void ActiveSpells::readState(const ESM::ActiveSpells& state) { for (const ESM::ActiveSpells::ActiveSpellParams& spell : state.mSpells) + { mSpells.emplace_back(ActiveSpellParams{ spell }); + // Generate ID for older saves that didn't have any. + if (mSpells.back().getActiveSpellId().empty()) + mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId()); + } for (const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue) mQueue.emplace_back(ActiveSpellParams{ spell }); } void ActiveSpells::unloadActor(const MWWorld::Ptr& ptr) { - purge( - [](const auto& spell) { - return spell.getType() == ESM::ActiveSpells::Type_Consumable - || spell.getType() == ESM::ActiveSpells::Type_Temporary; - }, - ptr); + purge([](const auto& spell) { return spell.hasFlag(ESM::ActiveSpells::Flag_Temporary); }, ptr); mQueue.clear(); } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index a505b8990a..e4fa60ddb6 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -33,12 +33,13 @@ namespace MWMechanics using ActiveEffect = ESM::ActiveEffect; class ActiveSpellParams { - ESM::RefId mId; + ESM::RefId mActiveSpellId; + ESM::RefId mSourceSpellId; std::vector mEffects; std::string mDisplayName; int mCasterActorId; ESM::RefNum mItem; - ESM::ActiveSpells::EffectType mType; + ESM::ActiveSpells::Flags mFlags; int mWorsenings; MWWorld::TimeStamp mNextWorsening; MWWorld::Ptr mSource; @@ -57,15 +58,17 @@ namespace MWMechanics friend class ActiveSpells; public: - ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster); + ActiveSpellParams( + const MWWorld::Ptr& caster, const ESM::RefId& id, std::string_view sourceName, ESM::RefNum item); + + ESM::RefId getActiveSpellId() const { return mActiveSpellId; } + void setActiveSpellId(ESM::RefId id) { mActiveSpellId = id; } - const ESM::RefId& getId() const { return mId; } + const ESM::RefId& getSourceSpellId() const { return mSourceSpellId; } const std::vector& getEffects() const { return mEffects; } std::vector& getEffects() { return mEffects; } - ESM::ActiveSpells::EffectType getType() const { return mType; } - int getCasterActorId() const { return mCasterActorId; } int getWorsenings() const { return mWorsenings; } @@ -75,6 +78,10 @@ namespace MWMechanics ESM::RefNum getItem() const { return mItem; } ESM::RefId getEnchantment() const; + const ESM::Spell* getSpell() const; + bool hasFlag(ESM::ActiveSpells::Flags flags) const; + void setFlag(ESM::ActiveSpells::Flags flags); + // Increments worsenings count and sets the next timestamp void worsen(); @@ -93,6 +100,8 @@ namespace MWMechanics TIterator end() const; + TIterator getActiveSpellById(const ESM::RefId& id); + void update(const MWWorld::Ptr& ptr, float duration); private: @@ -132,7 +141,9 @@ namespace MWMechanics void addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor); /// Removes the active effects from this spell/potion/.. with \a id - void removeEffects(const MWWorld::Ptr& ptr, const ESM::RefId& id); + void removeEffectsBySourceSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id); + /// Removes the active effects of a specific active spell + void removeEffectsByActiveSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id); /// Remove all active effects with this effect id void purgeEffect(const MWWorld::Ptr& ptr, int effectId, ESM::RefId effectArg = {}); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 506bf80bc4..addf62df34 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1228,11 +1228,11 @@ namespace MWMechanics } } - void Actors::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell) const + void Actors::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell) const { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) - iter->second->getCharacterController().castSpell(spellId, manualSpell); + iter->second->getCharacterController().castSpell(spellId, scriptedSpell); } bool Actors::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) const diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 14c55c4e45..2821df43e6 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -67,7 +67,7 @@ namespace MWMechanics void resurrect(const MWWorld::Ptr& ptr) const; - void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell = false) const; + void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell = false) const; void updateActor(const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) const; ///< Updates an actor with a new Ptr diff --git a/apps/openmw/mwmechanics/aicast.cpp b/apps/openmw/mwmechanics/aicast.cpp index 249ca97326..6384d70c06 100644 --- a/apps/openmw/mwmechanics/aicast.cpp +++ b/apps/openmw/mwmechanics/aicast.cpp @@ -25,11 +25,11 @@ namespace MWMechanics } } -MWMechanics::AiCast::AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool manualSpell) +MWMechanics::AiCast::AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool scriptedSpell) : mTargetId(targetId) , mSpellId(spellId) , mCasting(false) - , mManual(manualSpell) + , mScripted(scriptedSpell) , mDistance(getInitialDistance(spellId)) { } @@ -49,7 +49,7 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac if (target.isEmpty()) return true; - if (!mManual + if (!mScripted && !pathTo(actor, target.getRefData().getPosition().asVec3(), duration, characterController.getSupportedMovementDirections(), mDistance)) { @@ -85,7 +85,7 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac if (!mCasting) { - MWBase::Environment::get().getMechanicsManager()->castSpell(actor, mSpellId, mManual); + MWBase::Environment::get().getMechanicsManager()->castSpell(actor, mSpellId, mScripted); mCasting = true; return false; } diff --git a/apps/openmw/mwmechanics/aicast.hpp b/apps/openmw/mwmechanics/aicast.hpp index 435458cc0f..649c5a4d34 100644 --- a/apps/openmw/mwmechanics/aicast.hpp +++ b/apps/openmw/mwmechanics/aicast.hpp @@ -15,7 +15,7 @@ namespace MWMechanics class AiCast final : public TypedAiPackage { public: - AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool manualSpell = false); + AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool scriptedSpell = false); bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration) override; @@ -37,7 +37,7 @@ namespace MWMechanics const ESM::RefId mTargetId; const ESM::RefId mSpellId; bool mCasting; - const bool mManual; + const bool mScripted; const float mDistance; }; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 0b3c2b8bd2..2399961a3a 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -275,7 +275,7 @@ namespace MWMechanics if (!spellId.empty()) { const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(spellId); - if (spell->mEffects.mList.empty() || spell->mEffects.mList[0].mRange != ESM::RT_Target) + if (spell->mEffects.mList.empty() || spell->mEffects.mList[0].mData.mRange != ESM::RT_Target) canShout = false; } storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat, canShout); diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 563bd8b8cd..91d2a9bbb8 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -355,14 +355,14 @@ namespace MWMechanics { const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(selectedSpellId); - for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); + for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) { - if (effectIt->mRange == ESM::RT_Target) + if (effectIt->mData.mRange == ESM::RT_Target) { const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( - effectIt->mEffectID); + effectIt->mData.mEffectID); dist = effect->mData.mSpeed; break; } @@ -375,14 +375,14 @@ namespace MWMechanics { const ESM::Enchantment* ench = MWBase::Environment::get().getESMStore()->get().find(enchId); - for (std::vector::const_iterator effectIt = ench->mEffects.mList.begin(); + for (std::vector::const_iterator effectIt = ench->mEffects.mList.begin(); effectIt != ench->mEffects.mList.end(); ++effectIt) { - if (effectIt->mRange == ESM::RT_Target) + if (effectIt->mData.mRange == ESM::RT_Target) { const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( - effectIt->mEffectID); + effectIt->mData.mEffectID); dist = effect->mData.mSpeed; break; } diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 3adb399483..4be48296a9 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -262,13 +262,13 @@ const ESM::Potion* MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co for (size_t i = 0; i < iter->mEffects.mList.size(); ++i) { - const ESM::ENAMstruct& first = iter->mEffects.mList[i]; + const ESM::IndexedENAMstruct& first = iter->mEffects.mList[i]; const ESM::ENAMstruct& second = mEffects[i]; - if (first.mEffectID != second.mEffectID || first.mArea != second.mArea || first.mRange != second.mRange - || first.mSkill != second.mSkill || first.mAttribute != second.mAttribute - || first.mMagnMin != second.mMagnMin || first.mMagnMax != second.mMagnMax - || first.mDuration != second.mDuration) + if (first.mData.mEffectID != second.mEffectID || first.mData.mArea != second.mArea + || first.mData.mRange != second.mRange || first.mData.mSkill != second.mSkill + || first.mData.mAttribute != second.mAttribute || first.mData.mMagnMin != second.mMagnMin + || first.mData.mMagnMax != second.mMagnMax || first.mData.mDuration != second.mDuration) { mismatch = true; break; @@ -324,7 +324,7 @@ void MWMechanics::Alchemy::addPotion(const std::string& name) newRecord.mModel = "m\\misc_potion_" + std::string(meshes[index]) + "_01.nif"; newRecord.mIcon = "m\\tx_potion_" + std::string(meshes[index]) + "_01.dds"; - newRecord.mEffects.mList = mEffects; + newRecord.mEffects.populate(mEffects); const ESM::Potion* record = getRecord(newRecord); if (!record) diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index a2f6d479f1..5bab25fbe5 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -221,7 +221,7 @@ namespace MWMechanics for (const auto& spellEffect : spell->mEffects.mList) { const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(spellEffect.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(spellEffect.mData.mEffectID); static const int iAutoSpellAttSkillMin = MWBase::Environment::get() .getESMStore() ->get() @@ -230,7 +230,7 @@ namespace MWMechanics if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)) { - ESM::RefId skill = ESM::Skill::indexToRefId(spellEffect.mSkill); + ESM::RefId skill = ESM::Skill::indexToRefId(spellEffect.mData.mSkill); auto found = actorSkills.find(skill); if (found == actorSkills.end() || found->second.getBase() < iAutoSpellAttSkillMin) return false; @@ -238,7 +238,7 @@ namespace MWMechanics if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute)) { - ESM::RefId attribute = ESM::Attribute::indexToRefId(spellEffect.mAttribute); + ESM::RefId attribute = ESM::Attribute::indexToRefId(spellEffect.mData.mAttribute); auto found = actorAttributes.find(attribute); if (found == actorAttributes.end() || found->second.getBase() < iAutoSpellAttSkillMin) return false; @@ -253,22 +253,22 @@ namespace MWMechanics { // Morrowind for some reason uses a formula slightly different from magicka cost calculation float minChance = std::numeric_limits::max(); - for (const ESM::ENAMstruct& effect : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& effect : spell->mEffects.mList) { const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(effect.mData.mEffectID); int minMagn = 1; int maxMagn = 1; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) { - minMagn = effect.mMagnMin; - maxMagn = effect.mMagnMax; + minMagn = effect.mData.mMagnMin; + maxMagn = effect.mData.mMagnMax; } int duration = 0; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) - duration = effect.mDuration; + duration = effect.mData.mDuration; if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) duration = std::max(1, duration); @@ -281,10 +281,10 @@ namespace MWMechanics float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); x *= 0.1 * magicEffect->mData.mBaseCost; x *= 1 + duration; - x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost; + x += 0.05 * std::max(1, effect.mData.mArea) * magicEffect->mData.mBaseCost; x *= fEffectCostMult; - if (effect.mRange == ESM::RT_Target) + if (effect.mData.mRange == ESM::RT_Target) x *= 1.5f; float s = 0.f; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c0f6111a79..57e61e74aa 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1155,8 +1155,8 @@ namespace MWMechanics else if (groupname == "spellcast" && action == mAttackType + " release") { if (mCanCast) - MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); - mCastingManualSpell = false; + MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingScriptedSpell); + mCastingScriptedSpell = false; mCanCast = false; } else if (groupname == "containeropen" && action == "loot") @@ -1526,9 +1526,9 @@ namespace MWMechanics bool isMagicItem = false; // Play hand VFX and allow castSpell use (assuming an animation is going to be played) if - // spellcasting is successful. Manual spellcasting bypasses restrictions. + // spellcasting is successful. Scripted spellcasting bypasses restrictions. MWWorld::SpellCastState spellCastResult = MWWorld::SpellCastState::Success; - if (!mCastingManualSpell) + if (!mCastingScriptedSpell) spellCastResult = world->startSpellCast(mPtr); mCanCast = spellCastResult == MWWorld::SpellCastState::Success; @@ -1558,9 +1558,9 @@ namespace MWMechanics else if (!spellid.empty() && spellCastResult != MWWorld::SpellCastState::PowerAlreadyUsed) { world->breakInvisibility(mPtr); - MWMechanics::CastSpell cast(mPtr, {}, false, mCastingManualSpell); + MWMechanics::CastSpell cast(mPtr, {}, false, mCastingScriptedSpell); - const std::vector* effects{ nullptr }; + const std::vector* effects{ nullptr }; const MWWorld::ESMStore& store = world->getStore(); if (isMagicItem) { @@ -1579,7 +1579,7 @@ namespace MWMechanics if (mCanCast) { const ESM::MagicEffect* effect = store.get().find( - effects->back().mEffectID); // use last effect of list for color of VFX_Hands + effects->back().mData.mEffectID); // use last effect of list for color of VFX_Hands const ESM::Static* castStatic = world->getStore().get().find(ESM::RefId::stringRefId("VFX_Hands")); @@ -1593,7 +1593,7 @@ namespace MWMechanics "", false, "Bip01 R Hand", effect->mParticle); } // first effect used for casting animation - const ESM::ENAMstruct& firstEffect = effects->front(); + const ESM::ENAMstruct& firstEffect = effects->front().mData; std::string startKey; std::string stopKey; @@ -1602,9 +1602,9 @@ namespace MWMechanics startKey = "start"; stopKey = "stop"; if (mCanCast) - world->castSpell( - mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately - mCastingManualSpell = false; + world->castSpell(mPtr, + mCastingScriptedSpell); // No "release" text key to use, so cast immediately + mCastingScriptedSpell = false; mCanCast = false; } else @@ -2735,7 +2735,7 @@ namespace MWMechanics // Make sure we canceled the current attack or spellcasting, // because we disabled attack animations anyway. mCanCast = false; - mCastingManualSpell = false; + mCastingScriptedSpell = false; setAttackingOrSpell(false); if (mUpperBodyState != UpperBodyState::None) mUpperBodyState = UpperBodyState::WeaponEquipped; @@ -2887,7 +2887,7 @@ namespace MWMechanics bool CharacterController::isCastingSpell() const { - return mCastingManualSpell || mUpperBodyState == UpperBodyState::Casting; + return mCastingScriptedSpell || mUpperBodyState == UpperBodyState::Casting; } bool CharacterController::isReadyToBlock() const @@ -2941,10 +2941,10 @@ namespace MWMechanics mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(attackingOrSpell); } - void CharacterController::castSpell(const ESM::RefId& spellId, bool manualSpell) + void CharacterController::castSpell(const ESM::RefId& spellId, bool scriptedSpell) { setAttackingOrSpell(true); - mCastingManualSpell = manualSpell; + mCastingScriptedSpell = scriptedSpell; ActionSpell action = ActionSpell(spellId); action.prepare(mPtr); } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 430635fff5..8ead23f659 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -192,7 +192,7 @@ namespace MWMechanics bool mCanCast{ false }; - bool mCastingManualSpell{ false }; + bool mCastingScriptedSpell{ false }; bool mIsMovingBackward{ false }; osg::Vec2f mSmoothedSpeed; @@ -312,7 +312,7 @@ namespace MWMechanics bool isAttackingOrSpell() const; void setVisibility(float visibility) const; - void castSpell(const ESM::RefId& spellId, bool manualSpell = false); + void castSpell(const ESM::RefId& spellId, bool scriptedSpell = false); void setAIAttackType(std::string_view attackType); static std::string_view getRandomAttackType(); diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 1de55b0c8d..7d0007f9e3 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -198,13 +198,13 @@ namespace MWMechanics float enchantmentCost = 0.f; float cost = 0.f; - for (const ESM::ENAMstruct& effect : mEffectList.mList) + for (const ESM::IndexedENAMstruct& effect : mEffectList.mList) { - float baseCost = (store.get().find(effect.mEffectID))->mData.mBaseCost; - int magMin = std::max(1, effect.mMagnMin); - int magMax = std::max(1, effect.mMagnMax); - int area = std::max(1, effect.mArea); - float duration = static_cast(effect.mDuration); + float baseCost = (store.get().find(effect.mData.mEffectID))->mData.mBaseCost; + int magMin = std::max(1, effect.mData.mMagnMin); + int magMax = std::max(1, effect.mData.mMagnMax); + int area = std::max(1, effect.mData.mArea); + float duration = static_cast(effect.mData.mDuration); if (mCastStyle == ESM::Enchantment::ConstantEffect) duration = fEnchantmentConstantDurationMult; @@ -212,7 +212,7 @@ namespace MWMechanics cost = std::max(1.f, cost); - if (effect.mRange == ESM::RT_Target) + if (effect.mData.mRange == ESM::RT_Target) cost *= 1.5f; enchantmentCost += precise ? cost : std::floor(cost); @@ -244,13 +244,7 @@ namespace MWMechanics for (int i = 0; i < static_cast(iter->mEffects.mList.size()); ++i) { - const ESM::ENAMstruct& first = iter->mEffects.mList[i]; - const ESM::ENAMstruct& second = toFind.mEffects.mList[i]; - - if (first.mEffectID != second.mEffectID || first.mArea != second.mArea || first.mRange != second.mRange - || first.mSkill != second.mSkill || first.mAttribute != second.mAttribute - || first.mMagnMin != second.mMagnMin || first.mMagnMax != second.mMagnMax - || first.mDuration != second.mDuration) + if (iter->mEffects.mList[i] != toFind.mEffects.mList[i]) { mismatch = true; break; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 9319d030b8..47fba46c75 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -261,10 +261,10 @@ namespace MWMechanics mObjects.addObject(ptr); } - void MechanicsManager::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell) + void MechanicsManager::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell) { if (ptr.getClass().isActor()) - mActors.castSpell(ptr, spellId, manualSpell); + mActors.castSpell(ptr, spellId, scriptedSpell); } void MechanicsManager::remove(const MWWorld::Ptr& ptr, bool keepActive) @@ -1978,11 +1978,7 @@ namespace MWMechanics // Transforming removes all temporary effects actor.getClass().getCreatureStats(actor).getActiveSpells().purge( - [](const auto& params) { - return params.getType() == ESM::ActiveSpells::Type_Consumable - || params.getType() == ESM::ActiveSpells::Type_Temporary; - }, - actor); + [](const auto& params) { return params.hasFlag(ESM::ActiveSpells::Flag_Temporary); }, actor); mActors.updateActor(actor, 0.f); if (werewolf) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 516b778f1e..bf94589309 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -202,7 +202,7 @@ namespace MWMechanics /// Is \a ptr casting spell or using weapon now? bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const override; - void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell = false) override; + void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell = false) override; void processChangedSettings(const Settings::CategorySettingVector& settings) override; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 721131dcb0..2fd250f8c1 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -29,11 +29,11 @@ namespace MWMechanics { CastSpell::CastSpell( - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile, const bool manualSpell) + const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile, const bool scriptedSpell) : mCaster(caster) , mTarget(target) , mFromProjectile(fromProjectile) - , mManualSpell(manualSpell) + , mScriptedSpell(scriptedSpell) { } @@ -41,21 +41,21 @@ namespace MWMechanics const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const { const auto world = MWBase::Environment::get().getWorld(); - std::map> toApply; + std::map> toApply; int index = -1; - for (const ESM::ENAMstruct& effectInfo : effects.mList) + for (const ESM::IndexedENAMstruct& effectInfo : effects.mList) { ++index; - const ESM::MagicEffect* effect = world->getStore().get().find(effectInfo.mEffectID); + const ESM::MagicEffect* effect = world->getStore().get().find(effectInfo.mData.mEffectID); - if (effectInfo.mRange != rangeType - || (effectInfo.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor())) + if (effectInfo.mData.mRange != rangeType + || (effectInfo.mData.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor())) continue; // Not right range type, or not area effect and hit an actor - if (mFromProjectile && effectInfo.mArea <= 0) + if (mFromProjectile && effectInfo.mData.mArea <= 0) continue; // Don't play explosion for projectiles with 0-area effects - if (!mFromProjectile && effectInfo.mRange == ESM::RT_Touch && !ignore.isEmpty() + if (!mFromProjectile && effectInfo.mData.mRange == ESM::RT_Touch && !ignore.isEmpty() && !ignore.getClass().isActor() && !ignore.getClass().hasToolTip(ignore) && (mCaster.isEmpty() || mCaster.getClass().isActor())) continue; // Don't play explosion for touch spells on non-activatable objects except when spell is from @@ -70,16 +70,16 @@ namespace MWMechanics const std::string& texture = effect->mParticle; - if (effectInfo.mArea <= 0) + if (effectInfo.mData.mArea <= 0) { - if (effectInfo.mRange == ESM::RT_Target) + if (effectInfo.mData.mRange == ESM::RT_Target) world->spawnEffect( Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, 1.0f); continue; } else world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, - static_cast(effectInfo.mArea * 2)); + static_cast(effectInfo.mData.mArea * 2)); // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) { @@ -95,7 +95,7 @@ namespace MWMechanics std::vector objects; static const int unitsPerFoot = ceil(Constants::UnitsPerFoot); MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( - mHitPosition, static_cast(effectInfo.mArea * unitsPerFoot), objects); + mHitPosition, static_cast(effectInfo.mData.mArea * unitsPerFoot), objects); for (const MWWorld::Ptr& affected : objects) { // Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing @@ -104,13 +104,6 @@ namespace MWMechanics continue; auto& list = toApply[affected]; - while (list.size() < static_cast(index)) - { - // Insert dummy effects to preserve indices - auto& dummy = list.emplace_back(effectInfo); - dummy.mRange = ESM::RT_Self; - assert(dummy.mRange != rangeType); - } list.push_back(effectInfo); } } @@ -151,45 +144,34 @@ namespace MWMechanics void CastSpell::inflict( const MWWorld::Ptr& target, const ESM::EffectList& effects, ESM::RangeType range, bool exploded) const { + bool targetIsDeadActor = false; const bool targetIsActor = !target.isEmpty() && target.getClass().isActor(); if (targetIsActor) { - // Early-out for characters that have departed. const auto& stats = target.getClass().getCreatureStats(target); if (stats.isDead() && stats.isDeathAnimationFinished()) - return; + targetIsDeadActor = true; } // If none of the effects need to apply, we can early-out bool found = false; bool containsRecastable = false; - std::vector magicEffects; - magicEffects.reserve(effects.mList.size()); const auto& store = MWBase::Environment::get().getESMStore()->get(); - for (const ESM::ENAMstruct& effect : effects.mList) + for (const ESM::IndexedENAMstruct& effect : effects.mList) { - if (effect.mRange == range) + if (effect.mData.mRange == range) { found = true; - const ESM::MagicEffect* magicEffect = store.find(effect.mEffectID); - // caster needs to be an actor for linked effects (e.g. Absorb) - if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked - && (mCaster.isEmpty() || !mCaster.getClass().isActor())) - { - magicEffects.push_back(nullptr); - continue; - } + const ESM::MagicEffect* magicEffect = store.find(effect.mData.mEffectID); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable)) containsRecastable = true; - magicEffects.push_back(magicEffect); } - else - magicEffects.push_back(nullptr); } if (!found) return; - ActiveSpells::ActiveSpellParams params(*this, mCaster); + ActiveSpells::ActiveSpellParams params(mCaster, mId, mSourceName, mItem); + params.setFlag(mFlags); bool castByPlayer = (!mCaster.isEmpty() && mCaster == getPlayer()); const ActiveSpells* targetSpells = nullptr; @@ -204,31 +186,32 @@ namespace MWMechanics return; } - for (size_t currentEffectIndex = 0; !target.isEmpty() && currentEffectIndex < effects.mList.size(); - ++currentEffectIndex) + for (auto& enam : effects.mList) { - const ESM::ENAMstruct& enam = effects.mList[currentEffectIndex]; - if (enam.mRange != range) + if (enam.mData.mRange != range) continue; - - const ESM::MagicEffect* magicEffect = magicEffects[currentEffectIndex]; + const ESM::MagicEffect* magicEffect = store.find(enam.mData.mEffectID); if (!magicEffect) continue; + // caster needs to be an actor for linked effects (e.g. Absorb) + if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked + && (mCaster.isEmpty() || !mCaster.getClass().isActor())) + continue; ActiveSpells::ActiveEffect effect; - effect.mEffectId = enam.mEffectID; - effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mEffectId = enam.mData.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; effect.mMagnitude = 0.f; - effect.mMinMagnitude = enam.mMagnMin; - effect.mMaxMagnitude = enam.mMagnMax; + effect.mMinMagnitude = enam.mData.mMagnMin; + effect.mMaxMagnitude = enam.mData.mMagnMax; effect.mTimeLeft = 0.f; - effect.mEffectIndex = static_cast(currentEffectIndex); + effect.mEffectIndex = enam.mIndex; effect.mFlags = ESM::ActiveEffect::Flag_None; - if (mManualSpell) + if (mScriptedSpell) effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect; bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); - effect.mDuration = hasDuration ? static_cast(enam.mDuration) : 1.f; + effect.mDuration = hasDuration ? static_cast(enam.mData.mDuration) : 1.f; bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; if (!appliedOnce) @@ -240,8 +223,8 @@ namespace MWMechanics params.getEffects().emplace_back(effect); bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful - || enam.mEffectID == ESM::MagicEffect::RestoreHealth; - if (castByPlayer && target != mCaster && targetIsActor && effectAffectsHealth) + || enam.mData.mEffectID == ESM::MagicEffect::RestoreHealth; + if (castByPlayer && target != mCaster && targetIsActor && !targetIsDeadActor && effectAffectsHealth) { // If player is attempting to cast a harmful spell on or is healing a living target, show the target's // HP bar. @@ -262,7 +245,10 @@ namespace MWMechanics if (!params.getEffects().empty()) { if (targetIsActor) - target.getClass().getCreatureStats(target).getActiveSpells().addSpell(params); + { + if (!targetIsDeadActor) + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(params); + } else { // Apply effects instantly. We can ignore effect deletion since the entire params object gets @@ -336,7 +322,7 @@ namespace MWMechanics ESM::RefId school = ESM::Skill::Alteration; if (!enchantment->mEffects.mList.empty()) { - short effectId = enchantment->mEffects.mList.front().mEffectID; + short effectId = enchantment->mEffects.mList.front().mData.mEffectID; const ESM::MagicEffect* magicEffect = store->get().find(effectId); school = magicEffect->mData.mSchool; } @@ -387,7 +373,8 @@ namespace MWMechanics { mSourceName = potion->mName; mId = potion->mId; - mType = ESM::ActiveSpells::Type_Consumable; + mFlags = static_cast( + ESM::ActiveSpells::Flag_Temporary | ESM::ActiveSpells::Flag_Stackable); inflict(mCaster, potion->mEffects, ESM::RT_Self); @@ -403,7 +390,7 @@ namespace MWMechanics bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mManualSpell) + if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mScriptedSpell) { school = getSpellSchool(spell, mCaster); @@ -438,7 +425,7 @@ namespace MWMechanics stats.getSpells().usePower(spell); } - if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell)) + if (!mScriptedSpell && mCaster == getPlayer() && spellIncreasesSkill(spell)) mCaster.getClass().skillUsageSucceeded(mCaster, school, ESM::Skill::Spellcast_Success); // A non-actor doesn't play its spell cast effects from a character controller, so play them here @@ -458,62 +445,27 @@ namespace MWMechanics bool CastSpell::cast(const ESM::Ingredient* ingredient) { mId = ingredient->mId; - mType = ESM::ActiveSpells::Type_Consumable; + mFlags = static_cast( + ESM::ActiveSpells::Flag_Temporary | ESM::ActiveSpells::Flag_Stackable); mSourceName = ingredient->mName; - ESM::ENAMstruct effect; - effect.mEffectID = ingredient->mData.mEffectID[0]; - effect.mSkill = ingredient->mData.mSkills[0]; - effect.mAttribute = ingredient->mData.mAttributes[0]; - effect.mRange = ESM::RT_Self; - effect.mArea = 0; - - const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - const auto magicEffect = store.get().find(effect.mEffectID); - const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster); + auto effect = rollIngredientEffect(mCaster, ingredient, mCaster != getPlayer()); - float x = (mCaster.getClass().getSkill(mCaster, ESM::Skill::Alchemy) - + 0.2f * creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified() - + 0.1f * creatureStats.getAttribute(ESM::Attribute::Luck).getModified()) - * creatureStats.getFatigueTerm(); - - auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - int roll = Misc::Rng::roll0to99(prng); - if (roll > x) + if (effect) + inflict(mCaster, *effect, ESM::RT_Self); + else { // "X has no effect on you" - std::string message = store.get().find("sNotifyMessage50")->mValue.getString(); + std::string message = MWBase::Environment::get() + .getESMStore() + ->get() + .find("sNotifyMessage50") + ->mValue.getString(); message = Misc::StringUtils::format(message, ingredient->mName); MWBase::Environment::get().getWindowManager()->messageBox(message); return false; } - float magnitude = 0; - float y = roll / std::min(x, 100.f); - y *= 0.25f * x; - if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) - effect.mDuration = 1; - else - effect.mDuration = static_cast(y); - if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) - { - if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) - magnitude = floor((0.05f * y) / (0.1f * magicEffect->mData.mBaseCost)); - else - magnitude = floor(y / (0.1f * magicEffect->mData.mBaseCost)); - magnitude = std::max(1.f, magnitude); - } - else - magnitude = 1; - - effect.mMagnMax = static_cast(magnitude); - effect.mMagnMin = static_cast(magnitude); - - ESM::EffectList effects; - effects.mList.push_back(effect); - - inflict(mCaster, effects, ESM::RT_Self); - return true; } @@ -527,14 +479,14 @@ namespace MWMechanics playSpellCastingEffects(spell->mEffects.mList); } - void CastSpell::playSpellCastingEffects(const std::vector& effects) const + void CastSpell::playSpellCastingEffects(const std::vector& effects) const { const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); std::vector addedEffects; - for (const ESM::ENAMstruct& effectData : effects) + for (const ESM::IndexedENAMstruct& effectData : effects) { - const auto effect = store.get().find(effectData.mEffectID); + const auto effect = store.get().find(effectData.mData.mEffectID); const ESM::Static* castStatic; @@ -587,7 +539,7 @@ namespace MWMechanics } if (animation && !mCaster.getClass().isActor()) - animation->addSpellCastGlow(effect); + animation->addSpellCastGlow(effect->getColor()); addedEffects.push_back(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel)); diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 8f10066e04..23d3b80713 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -26,7 +26,7 @@ namespace MWMechanics MWWorld::Ptr mCaster; // May be empty MWWorld::Ptr mTarget; // May be empty - void playSpellCastingEffects(const std::vector& effects) const; + void playSpellCastingEffects(const std::vector& effects) const; void explodeSpell(const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const; @@ -41,13 +41,13 @@ namespace MWMechanics false }; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false) bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon) - bool mManualSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, - // etc.) + bool mScriptedSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, + // etc.) ESM::RefNum mItem; - ESM::ActiveSpells::EffectType mType{ ESM::ActiveSpells::Type_Temporary }; + ESM::ActiveSpells::Flags mFlags{ ESM::ActiveSpells::Flag_Temporary }; CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile = false, - const bool manualSpell = false); + const bool scriptedSpell = false); bool cast(const ESM::Spell* spell); diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 8c415949f5..96044ebc5b 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -289,7 +289,7 @@ namespace animation->addEffect(Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel), ESM::MagicEffect::indexToName(ESM::MagicEffect::SpellAbsorption), false); int spellCost = 0; - if (const ESM::Spell* spell = esmStore.get().search(spellParams.getId())) + if (const ESM::Spell* spell = esmStore.get().search(spellParams.getSourceSpellId())) { spellCost = MWMechanics::calcSpellCost(*spell); } @@ -314,8 +314,7 @@ namespace auto& stats = target.getClass().getCreatureStats(target); auto& magnitudes = stats.getMagicEffects(); // Apply reflect and spell absorption - if (target != caster && spellParams.getType() != ESM::ActiveSpells::Type_Enchantment - && spellParams.getType() != ESM::ActiveSpells::Type_Permanent) + if (target != caster && spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary)) { bool canReflect = !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable) && !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Reflect) @@ -358,9 +357,8 @@ namespace // Apply resistances if (!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances)) { - const ESM::Spell* spell = nullptr; - if (spellParams.getType() == ESM::ActiveSpells::Type_Temporary) - spell = MWBase::Environment::get().getESMStore()->get().search(spellParams.getId()); + const ESM::Spell* spell + = spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary) ? spellParams.getSpell() : nullptr; float magnitudeMult = MWMechanics::getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes); if (magnitudeMult == 0) @@ -429,10 +427,9 @@ namespace MWMechanics // Dispel removes entire spells at once target.getClass().getCreatureStats(target).getActiveSpells().purge( [magnitude = effect.mMagnitude](const ActiveSpells::ActiveSpellParams& params) { - if (params.getType() == ESM::ActiveSpells::Type_Temporary) + if (params.hasFlag(ESM::ActiveSpells::Flag_Temporary)) { - const ESM::Spell* spell - = MWBase::Environment::get().getESMStore()->get().search(params.getId()); + const ESM::Spell* spell = params.getSpell(); if (spell && spell->mData.mType == ESM::Spell::ST_Spell) { auto& prng = MWBase::Environment::get().getWorld()->getPrng(); @@ -645,7 +642,7 @@ namespace MWMechanics else if (effect.mEffectId == ESM::MagicEffect::DamageFatigue) index = 2; // Damage "Dynamic" abilities reduce the base value - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) modDynamicStat(target, index, -effect.mMagnitude); else { @@ -666,7 +663,7 @@ namespace MWMechanics else if (!godmode) { // Damage Skill abilities reduce base skill :todd: - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { auto& npcStats = target.getClass().getNpcStats(target); SkillValue& skill = npcStats.getSkill(effect.getSkillOrAttribute()); @@ -725,7 +722,7 @@ namespace MWMechanics case ESM::MagicEffect::FortifyHealth: case ESM::MagicEffect::FortifyMagicka: case ESM::MagicEffect::FortifyFatigue: - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude); else adjustDynamicStat( @@ -737,7 +734,7 @@ namespace MWMechanics break; case ESM::MagicEffect::FortifyAttribute: // Abilities affect base stats, but not for drain - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { auto& creatureStats = target.getClass().getCreatureStats(target); auto attribute = effect.getSkillOrAttribute(); @@ -757,7 +754,7 @@ namespace MWMechanics case ESM::MagicEffect::FortifySkill: if (!target.getClass().isNpc()) invalid = true; - else if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + else if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { // Abilities affect base stats, but not for drain auto& npcStats = target.getClass().getNpcStats(target); @@ -922,7 +919,7 @@ namespace MWMechanics { MWRender::Animation* animation = world->getAnimation(target); if (animation) - animation->addSpellCastGlow(magicEffect); + animation->addSpellCastGlow(magicEffect->getColor()); int magnitude = static_cast(roll(effect)); if (target.getCellRef().getLockLevel() < magnitude) // If the door is not already locked to a higher value, lock it to spell magnitude @@ -947,7 +944,7 @@ namespace MWMechanics MWRender::Animation* animation = world->getAnimation(target); if (animation) - animation->addSpellCastGlow(magicEffect); + animation->addSpellCastGlow(magicEffect->getColor()); int magnitude = static_cast(roll(effect)); if (target.getCellRef().getLockLevel() <= magnitude) { @@ -985,7 +982,7 @@ namespace MWMechanics return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth }; auto& stats = target.getClass().getCreatureStats(target); auto& magnitudes = stats.getMagicEffects(); - if (spellParams.getType() != ESM::ActiveSpells::Type_Ability + if (!spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues) && !(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) { MagicApplicationResult::Type result @@ -998,10 +995,9 @@ namespace MWMechanics oldMagnitude = effect.mMagnitude; else { - if (spellParams.getType() != ESM::ActiveSpells::Type_Enchantment) - playEffects(target, *magicEffect, - spellParams.getType() == ESM::ActiveSpells::Type_Consumable - || spellParams.getType() == ESM::ActiveSpells::Type_Temporary); + if (!spellParams.hasFlag(ESM::ActiveSpells::Flag_Equipment) + && !spellParams.hasFlag(ESM::ActiveSpells::Flag_Lua)) + playEffects(target, *magicEffect, spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary)); if (effect.mEffectId == ESM::MagicEffect::Soultrap && !target.getClass().isNpc() && target.getType() == ESM::Creature::sRecordId && target.get()->mBase->mData.mSoul == 0 && caster == getPlayer()) @@ -1016,8 +1012,7 @@ namespace MWMechanics if (effect.mDuration != 0) { float mult = dt; - if (spellParams.getType() == ESM::ActiveSpells::Type_Consumable - || spellParams.getType() == ESM::ActiveSpells::Type_Temporary) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary)) mult = std::min(effect.mTimeLeft, dt); effect.mMagnitude *= mult; } @@ -1195,7 +1190,7 @@ namespace MWMechanics case ESM::MagicEffect::FortifyHealth: case ESM::MagicEffect::FortifyMagicka: case ESM::MagicEffect::FortifyFatigue: - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude); else adjustDynamicStat( @@ -1206,7 +1201,7 @@ namespace MWMechanics break; case ESM::MagicEffect::FortifyAttribute: // Abilities affect base stats, but not for drain - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { auto& creatureStats = target.getClass().getCreatureStats(target); auto attribute = effect.getSkillOrAttribute(); @@ -1222,7 +1217,7 @@ namespace MWMechanics break; case ESM::MagicEffect::FortifySkill: // Abilities affect base stats, but not for drain - if (spellParams.getType() == ESM::ActiveSpells::Type_Ability) + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { auto& npcStats = target.getClass().getNpcStats(target); auto& skill = npcStats.getSkill(effect.getSkillOrAttribute()); diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 776381e6b2..d1407c4cbd 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -34,7 +34,7 @@ namespace if (effectFilter == -1) { const ESM::Spell* spell - = MWBase::Environment::get().getESMStore()->get().search(it->getId()); + = MWBase::Environment::get().getESMStore()->get().search(it->getSourceSpellId()); if (!spell || spell->mData.mType != ESM::Spell::ST_Spell) continue; } @@ -67,7 +67,7 @@ namespace const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells(); for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it) { - if (it->getId() != spellId) + if (it->getSourceSpellId() != spellId) continue; const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it; @@ -85,7 +85,7 @@ namespace int actorId = caster.getClass().getCreatureStats(caster).getActorId(); const auto& active = target.getClass().getCreatureStats(target).getActiveSpells(); return std::find_if(active.begin(), active.end(), [&](const auto& spell) { - return spell.getCasterActorId() == actorId && spell.getId() == id; + return spell.getCasterActorId() == actorId && spell.getSourceSpellId() == id; }) != active.end(); } @@ -110,13 +110,13 @@ namespace MWMechanics int getRangeTypes(const ESM::EffectList& effects) { int types = 0; - for (std::vector::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) + for (const ESM::IndexedENAMstruct& effect : effects.mList) { - if (it->mRange == ESM::RT_Self) + if (effect.mData.mRange == ESM::RT_Self) types |= RangeTypes::Self; - else if (it->mRange == ESM::RT_Touch) + else if (effect.mData.mRange == ESM::RT_Touch) types |= RangeTypes::Touch; - else if (it->mRange == ESM::RT_Target) + else if (effect.mData.mRange == ESM::RT_Target) types |= RangeTypes::Target; } return types; @@ -735,12 +735,12 @@ namespace MWMechanics static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->mValue.getFloat(); static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->mValue.getFloat(); - for (const ESM::ENAMstruct& effect : list.mList) + for (const ESM::IndexedENAMstruct& effect : list.mList) { - float effectRating = rateEffect(effect, actor, enemy); + float effectRating = rateEffect(effect.mData, actor, enemy); if (useSpellMult) { - if (effect.mRange == ESM::RT_Target) + if (effect.mData.mRange == ESM::RT_Target) effectRating *= fAIRangeMagicSpellMult; else effectRating *= fAIMagicSpellMult; @@ -760,10 +760,10 @@ namespace MWMechanics float mult = fAIMagicSpellMult; - for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); + for (std::vector::const_iterator effectIt = spell->mEffects.mList.begin(); effectIt != spell->mEffects.mList.end(); ++effectIt) { - if (effectIt->mRange == ESM::RT_Target) + if (effectIt->mData.mRange == ESM::RT_Target) { if (!MWBase::Environment::get().getWorld()->isSwimming(enemy)) mult = fAIRangeMagicSpellMult; diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 12da7cdde8..26f0ebe4a2 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -174,7 +174,7 @@ namespace MWMechanics { for (const auto& effectIt : spell->mEffects.mList) { - if (effectIt.mEffectID == ESM::MagicEffect::Corprus) + if (effectIt.mData.mEffectID == ESM::MagicEffect::Corprus) { return true; } diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index 671939cb00..022aaec262 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "../mwbase/environment.hpp" @@ -23,13 +24,13 @@ namespace MWMechanics { float cost = 0; - for (const ESM::ENAMstruct& effect : list.mList) + for (const ESM::IndexedENAMstruct& effect : list.mList) { - float effectCost = std::max(0.f, MWMechanics::calcEffectCost(effect, nullptr, method)); + float effectCost = std::max(0.f, MWMechanics::calcEffectCost(effect.mData, nullptr, method)); // This is applied to the whole spell cost for each effect when // creating spells, but is only applied on the effect itself in TES:CS. - if (effect.mRange == ESM::RT_Target) + if (effect.mData.mRange == ESM::RT_Target) effectCost *= 1.5; cost += effectCost; @@ -158,25 +159,83 @@ namespace MWMechanics return potion.mData.mValue; } + std::optional rollIngredientEffect( + MWWorld::Ptr caster, const ESM::Ingredient* ingredient, uint32_t index) + { + if (index >= 4) + throw std::range_error("Index out of range"); + + ESM::ENAMstruct effect; + effect.mEffectID = ingredient->mData.mEffectID[index]; + effect.mSkill = ingredient->mData.mSkills[index]; + effect.mAttribute = ingredient->mData.mAttributes[index]; + effect.mRange = ESM::RT_Self; + effect.mArea = 0; + + if (effect.mEffectID < 0) + return std::nullopt; + + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const auto magicEffect = store.get().find(effect.mEffectID); + const MWMechanics::CreatureStats& creatureStats = caster.getClass().getCreatureStats(caster); + + float x = (caster.getClass().getSkill(caster, ESM::Skill::Alchemy) + + 0.2f * creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified() + + 0.1f * creatureStats.getAttribute(ESM::Attribute::Luck).getModified()) + * creatureStats.getFatigueTerm(); + + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::roll0to99(prng); + if (roll > x) + { + return std::nullopt; + } + + float magnitude = 0; + float y = roll / std::min(x, 100.f); + y *= 0.25f * x; + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + effect.mDuration = 1; + else + effect.mDuration = static_cast(y); + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) + { + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) + magnitude = floor((0.05f * y) / (0.1f * magicEffect->mData.mBaseCost)); + else + magnitude = floor(y / (0.1f * magicEffect->mData.mBaseCost)); + magnitude = std::max(1.f, magnitude); + } + else + magnitude = 1; + + effect.mMagnMax = static_cast(magnitude); + effect.mMagnMin = static_cast(magnitude); + + ESM::EffectList effects; + effects.mList.push_back({ effect, index }); + return effects; + } + float calcSpellBaseSuccessChance(const ESM::Spell* spell, const MWWorld::Ptr& actor, ESM::RefId* effectiveSchool) { // Morrowind for some reason uses a formula slightly different from magicka cost calculation float y = std::numeric_limits::max(); float lowestSkill = 0; - for (const ESM::ENAMstruct& effect : spell->mEffects.mList) + for (const ESM::IndexedENAMstruct& effect : spell->mEffects.mList) { - float x = static_cast(effect.mDuration); + float x = static_cast(effect.mData.mDuration); const auto magicEffect - = MWBase::Environment::get().getESMStore()->get().find(effect.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(effect.mData.mEffectID); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) x = std::max(1.f, x); x *= 0.1f * magicEffect->mData.mBaseCost; - x *= 0.5f * (effect.mMagnMin + effect.mMagnMax); - x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; - if (effect.mRange == ESM::RT_Target) + x *= 0.5f * (effect.mData.mMagnMin + effect.mData.mMagnMax); + x += effect.mData.mArea * 0.05f * magicEffect->mData.mBaseCost; + if (effect.mData.mRange == ESM::RT_Target) x *= 1.5f; static const float fEffectCostMult = MWBase::Environment::get() .getESMStore() diff --git a/apps/openmw/mwmechanics/spellutil.hpp b/apps/openmw/mwmechanics/spellutil.hpp index fa9b0c64b9..fb9d14c8a5 100644 --- a/apps/openmw/mwmechanics/spellutil.hpp +++ b/apps/openmw/mwmechanics/spellutil.hpp @@ -3,10 +3,14 @@ #include +#include + namespace ESM { + struct EffectList; struct ENAMstruct; struct Enchantment; + struct Ingredient; struct MagicEffect; struct Potion; struct Spell; @@ -36,6 +40,8 @@ namespace MWMechanics int getEnchantmentCharge(const ESM::Enchantment& enchantment); int getPotionValue(const ESM::Potion& potion); + std::optional rollIngredientEffect( + MWWorld::Ptr caster, const ESM::Ingredient* ingredient, uint32_t index = 0); /** * @param spell spell to cast diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 9cdbb19a98..101d4a3ea3 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1512,7 +1512,7 @@ namespace MWRender return mObjectRoot.get(); } - void Animation::addSpellCastGlow(const ESM::MagicEffect* effect, float glowDuration) + void Animation::addSpellCastGlow(const osg::Vec4f& color, float glowDuration) { if (!mGlowUpdater || (mGlowUpdater->isDone() || (mGlowUpdater->isPermanentGlowUpdater() == true))) { @@ -1521,12 +1521,11 @@ namespace MWRender if (mGlowUpdater && mGlowUpdater->isPermanentGlowUpdater()) { - mGlowUpdater->setColor(effect->getColor()); + mGlowUpdater->setColor(color); mGlowUpdater->setDuration(glowDuration); } else - mGlowUpdater - = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, effect->getColor(), glowDuration); + mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, color, glowDuration); } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index a2226a3054..22b7167a9c 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -345,7 +345,7 @@ namespace MWRender // Add a spell casting glow to an object. From measuring video taken from the original engine, // the glow seems to be about 1.5 seconds except for telekinesis, which is 1 second. - void addSpellCastGlow(const ESM::MagicEffect* effect, float glowDuration = 1.5); + void addSpellCastGlow(const osg::Vec4f& color, float glowDuration = 1.5); virtual void updatePtr(const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 4bc59e1524..064e90f114 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -560,7 +560,7 @@ namespace MWScript runtime.pop(); if (ptr.getClass().isActor()) - ptr.getClass().getCreatureStats(ptr).getActiveSpells().removeEffects(ptr, spellid); + ptr.getClass().getCreatureStats(ptr).getActiveSpells().removeEffectsBySourceSpellId(ptr, spellid); } }; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 932c290aaa..a1eec196eb 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -532,7 +532,7 @@ namespace MWWorld return result; const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getESMStore()->get().search( - enchantment->mEffects.mList.front().mEffectID); + enchantment->mEffects.mList.front().mData.mEffectID); if (!magicEffect) return result; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 7ecaaa217d..ff3c73311e 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -171,31 +171,31 @@ namespace auto iter = spell.mEffects.mList.begin(); while (iter != spell.mEffects.mList.end()) { - const ESM::MagicEffect* mgef = magicEffects.search(iter->mEffectID); + const ESM::MagicEffect* mgef = magicEffects.search(iter->mData.mEffectID); if (!mgef) { Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId - << ": dropping invalid effect (index " << iter->mEffectID << ")"; + << ": dropping invalid effect (index " << iter->mData.mEffectID << ")"; iter = spell.mEffects.mList.erase(iter); changed = true; continue; } - if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) && iter->mAttribute != -1) + if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) && iter->mData.mAttribute != -1) { - iter->mAttribute = -1; + iter->mData.mAttribute = -1; Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId << ": dropping unexpected attribute argument of " - << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect"; + << ESM::MagicEffect::indexToGmstString(iter->mData.mEffectID) << " effect"; changed = true; } - if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) && iter->mSkill != -1) + if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) && iter->mData.mSkill != -1) { - iter->mSkill = -1; + iter->mData.mSkill = -1; Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId << ": dropping unexpected skill argument of " - << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect"; + << ESM::MagicEffect::indexToGmstString(iter->mData.mEffectID) << " effect"; changed = true; } @@ -742,7 +742,16 @@ namespace MWWorld case ESM::REC_DYNA: reader.getSubNameIs("COUN"); - reader.getHT(mDynamicCount); + if (reader.getFormatVersion() <= ESM::MaxActiveSpellTypeVersion) + { + uint32_t dynamicCount32 = 0; + reader.getHT(dynamicCount32); + mDynamicCount = dynamicCount32; + } + else + { + reader.getHT(mDynamicCount); + } return true; default: diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 16062c97db..c6271a4428 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -162,7 +162,7 @@ namespace MWWorld std::vector mStores; std::vector mDynamicStores; - unsigned int mDynamicCount; + uint64_t mDynamicCount; mutable std::unordered_map> mSpellListCache; @@ -209,6 +209,7 @@ namespace MWWorld void clearDynamic(); void rebuildIdsIndex(); + ESM::RefId generateId() { return ESM::RefId::generated(mDynamicCount++); } void movePlayerRecord(); @@ -229,7 +230,7 @@ namespace MWWorld template const T* insert(const T& x) { - const ESM::RefId id = ESM::RefId::generated(mDynamicCount++); + const ESM::RefId id = generateId(); Store& store = getWritable(); if (store.search(id) != nullptr) diff --git a/apps/openmw/mwworld/magiceffects.cpp b/apps/openmw/mwworld/magiceffects.cpp index 4ff0e60c46..ce83b3e18f 100644 --- a/apps/openmw/mwworld/magiceffects.cpp +++ b/apps/openmw/mwworld/magiceffects.cpp @@ -46,8 +46,8 @@ namespace MWWorld for (auto& effect : spell->mEffects.mList) { - if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) - stats.mWorsenings[effect.mAttribute] = oldStats.mWorsenings; + if (effect.mData.mEffectID == ESM::MagicEffect::DrainAttribute) + stats.mWorsenings[effect.mData.mAttribute] = oldStats.mWorsenings; } creatureStats.mCorprusSpells[id] = stats; } @@ -58,30 +58,30 @@ namespace MWWorld if (!spell || spell->mData.mType == ESM::Spell::ST_Spell || spell->mData.mType == ESM::Spell::ST_Power) continue; ESM::ActiveSpells::ActiveSpellParams params; - params.mId = id; + params.mSourceSpellId = id; params.mDisplayName = spell->mName; params.mCasterActorId = creatureStats.mActorId; if (spell->mData.mType == ESM::Spell::ST_Ability) - params.mType = ESM::ActiveSpells::Type_Ability; + params.mFlags = ESM::Compatibility::ActiveSpells::Type_Ability_Flags; else - params.mType = ESM::ActiveSpells::Type_Permanent; + params.mFlags = ESM::Compatibility::ActiveSpells::Type_Permanent_Flags; params.mWorsenings = -1; params.mNextWorsening = ESM::TimeStamp(); - int effectIndex = 0; for (const auto& enam : spell->mEffects.mList) { - if (oldParams.mPurgedEffects.find(effectIndex) == oldParams.mPurgedEffects.end()) + if (oldParams.mPurgedEffects.find(enam.mIndex) == oldParams.mPurgedEffects.end()) { ESM::ActiveEffect effect; - effect.mEffectId = enam.mEffectID; - effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mEffectId = enam.mData.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; effect.mDuration = -1; effect.mTimeLeft = -1; - effect.mEffectIndex = effectIndex; - auto rand = oldParams.mEffectRands.find(effectIndex); + effect.mEffectIndex = enam.mIndex; + auto rand = oldParams.mEffectRands.find(enam.mIndex); if (rand != oldParams.mEffectRands.end()) { - float magnitude = (enam.mMagnMax - enam.mMagnMin) * rand->second + enam.mMagnMin; + float magnitude + = (enam.mData.mMagnMax - enam.mData.mMagnMin) * rand->second + enam.mData.mMagnMin; effect.mMagnitude = magnitude; effect.mMinMagnitude = magnitude; effect.mMaxMagnitude = magnitude; @@ -92,13 +92,12 @@ namespace MWWorld else { effect.mMagnitude = 0.f; - effect.mMinMagnitude = enam.mMagnMin; - effect.mMaxMagnitude = enam.mMagnMax; + effect.mMinMagnitude = enam.mData.mMagnMin; + effect.mMaxMagnitude = enam.mData.mMagnMax; effect.mFlags = ESM::ActiveEffect::Flag_None; } params.mEffects.emplace_back(effect); } - effectIndex++; } creatureStats.mActiveSpells.mSpells.emplace_back(params); } @@ -132,30 +131,28 @@ namespace MWWorld if (!enchantment) continue; ESM::ActiveSpells::ActiveSpellParams params; - params.mId = id; + params.mSourceSpellId = id; params.mDisplayName = std::move(name); params.mCasterActorId = creatureStats.mActorId; - params.mType = ESM::ActiveSpells::Type_Enchantment; + params.mFlags = ESM::Compatibility::ActiveSpells::Type_Enchantment_Flags; params.mWorsenings = -1; params.mNextWorsening = ESM::TimeStamp(); - for (std::size_t effectIndex = 0; - effectIndex < oldMagnitudes.size() && effectIndex < enchantment->mEffects.mList.size(); ++effectIndex) + for (const auto& enam : enchantment->mEffects.mList) { - const auto& enam = enchantment->mEffects.mList[effectIndex]; - auto [random, multiplier] = oldMagnitudes[effectIndex]; - float magnitude = (enam.mMagnMax - enam.mMagnMin) * random + enam.mMagnMin; + auto [random, multiplier] = oldMagnitudes[enam.mIndex]; + float magnitude = (enam.mData.mMagnMax - enam.mData.mMagnMin) * random + enam.mData.mMagnMin; magnitude *= multiplier; if (magnitude <= 0) continue; ESM::ActiveEffect effect; - effect.mEffectId = enam.mEffectID; + effect.mEffectId = enam.mData.mEffectID; effect.mMagnitude = magnitude; effect.mMinMagnitude = magnitude; effect.mMaxMagnitude = magnitude; - effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mArg = MWMechanics::EffectKey(enam.mData).mArg; effect.mDuration = -1; effect.mTimeLeft = -1; - effect.mEffectIndex = static_cast(effectIndex); + effect.mEffectIndex = enam.mIndex; // Prevent recalculation of resistances and don't reflect or absorb the effect effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances | ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption; @@ -172,7 +169,7 @@ namespace MWWorld { auto it = std::find_if(creatureStats.mActiveSpells.mSpells.begin(), creatureStats.mActiveSpells.mSpells.end(), - [&](const auto& params) { return params.mId == spell.first; }); + [&](const auto& params) { return params.mSourceSpellId == spell.first; }); if (it != creatureStats.mActiveSpells.mSpells.end()) { it->mNextWorsening = spell.second.mNextWorsening; @@ -188,7 +185,7 @@ namespace MWWorld continue; for (auto& params : creatureStats.mActiveSpells.mSpells) { - if (params.mId == key.mSourceId) + if (params.mSourceSpellId == key.mSourceId) { bool found = false; for (auto& effect : params.mEffects) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 6fc515981b..d6715c5ad2 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -79,18 +79,17 @@ namespace int count = 0; speed = 0.0f; ESM::EffectList projectileEffects; - for (std::vector::const_iterator iter(effects->mList.begin()); iter != effects->mList.end(); - ++iter) + for (const ESM::IndexedENAMstruct& effect : effects->mList) { const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(iter->mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(effect.mData.mEffectID); // Speed of multi-effect projectiles should be the average of the constituent effects, // based on observation of the original engine. speed += magicEffect->mData.mSpeed; count++; - if (iter->mRange != ESM::RT_Target) + if (effect.mData.mRange != ESM::RT_Target) continue; if (magicEffect->mBolt.empty()) @@ -106,7 +105,7 @@ namespace ->get() .find(magicEffect->mData.mSchool) ->mSchool->mBoltSound); - projectileEffects.mList.push_back(*iter); + projectileEffects.mList.push_back(effect); } if (count != 0) @@ -117,7 +116,7 @@ namespace { const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getESMStore()->get().find( - effects->mList.begin()->mEffectID); + effects->mList.begin()->mData.mEffectID); texture = magicEffect->mParticle; } @@ -136,10 +135,10 @@ namespace { // Calculate combined light diffuse color from magical effects osg::Vec4 lightDiffuseColor; - for (const ESM::ENAMstruct& enam : effects.mList) + for (const ESM::IndexedENAMstruct& enam : effects.mList) { const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(enam.mEffectID); + = MWBase::Environment::get().getESMStore()->get().find(enam.mData.mEffectID); lightDiffuseColor += magicEffect->getColor(); } int numberOfEffects = effects.mList.size(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0468b36a2f..9ca8a0d12a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2935,14 +2935,14 @@ namespace MWWorld return result; } - void World::castSpell(const Ptr& actor, bool manualSpell) + void World::castSpell(const Ptr& actor, bool scriptedSpell) { MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); const bool casterIsPlayer = actor == MWMechanics::getPlayer(); MWWorld::Ptr target; // For scripted spells we should not use hit contact - if (manualSpell) + if (scriptedSpell) { if (!casterIsPlayer) { @@ -3010,7 +3010,7 @@ namespace MWWorld const ESM::RefId& selectedSpell = stats.getSpells().getSelectedSpell(); - MWMechanics::CastSpell cast(actor, target, false, manualSpell); + MWMechanics::CastSpell cast(actor, target, false, scriptedSpell); cast.mHitPosition = hitPosition; if (!selectedSpell.empty()) @@ -3698,22 +3698,22 @@ namespace MWWorld void World::preloadEffects(const ESM::EffectList* effectList) { - for (const ESM::ENAMstruct& effectInfo : effectList->mList) + for (const ESM::IndexedENAMstruct& effectInfo : effectList->mList) { - const ESM::MagicEffect* effect = mStore.get().find(effectInfo.mEffectID); + const ESM::MagicEffect* effect = mStore.get().find(effectInfo.mData.mEffectID); - if (MWMechanics::isSummoningEffect(effectInfo.mEffectID)) + if (MWMechanics::isSummoningEffect(effectInfo.mData.mEffectID)) { preload(mWorldScene.get(), mStore, ESM::RefId::stringRefId("VFX_Summon_Start")); - preload(mWorldScene.get(), mStore, MWMechanics::getSummonedCreature(effectInfo.mEffectID)); + preload(mWorldScene.get(), mStore, MWMechanics::getSummonedCreature(effectInfo.mData.mEffectID)); } preload(mWorldScene.get(), mStore, effect->mCasting); preload(mWorldScene.get(), mStore, effect->mHit); - if (effectInfo.mArea > 0) + if (effectInfo.mData.mArea > 0) preload(mWorldScene.get(), mStore, effect->mArea); - if (effectInfo.mRange == ESM::RT_Target) + if (effectInfo.mData.mRange == ESM::RT_Target) preload(mWorldScene.get(), mStore, effect->mBolt); } } diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index 6d5fdf1c14..2629442563 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -530,29 +530,30 @@ namespace ESM TEST_P(Esm3SaveLoadRecordTest, enamShouldNotChange) { EffectList record; - record.mList.emplace_back(ENAMstruct{ - .mEffectID = 1, - .mSkill = 2, - .mAttribute = 3, - .mRange = 4, - .mArea = 5, - .mDuration = 6, - .mMagnMin = 7, - .mMagnMax = 8, - }); + record.mList.emplace_back(IndexedENAMstruct{ { + .mEffectID = 1, + .mSkill = 2, + .mAttribute = 3, + .mRange = 4, + .mArea = 5, + .mDuration = 6, + .mMagnMin = 7, + .mMagnMax = 8, + }, + 0 }); EffectList result; saveAndLoadRecord(record, GetParam(), result); EXPECT_EQ(result.mList.size(), record.mList.size()); - EXPECT_EQ(result.mList[0].mEffectID, record.mList[0].mEffectID); - EXPECT_EQ(result.mList[0].mSkill, record.mList[0].mSkill); - EXPECT_EQ(result.mList[0].mAttribute, record.mList[0].mAttribute); - EXPECT_EQ(result.mList[0].mRange, record.mList[0].mRange); - EXPECT_EQ(result.mList[0].mArea, record.mList[0].mArea); - EXPECT_EQ(result.mList[0].mDuration, record.mList[0].mDuration); - EXPECT_EQ(result.mList[0].mMagnMin, record.mList[0].mMagnMin); - EXPECT_EQ(result.mList[0].mMagnMax, record.mList[0].mMagnMax); + EXPECT_EQ(result.mList[0].mData.mEffectID, record.mList[0].mData.mEffectID); + EXPECT_EQ(result.mList[0].mData.mSkill, record.mList[0].mData.mSkill); + EXPECT_EQ(result.mList[0].mData.mAttribute, record.mList[0].mData.mAttribute); + EXPECT_EQ(result.mList[0].mData.mRange, record.mList[0].mData.mRange); + EXPECT_EQ(result.mList[0].mData.mArea, record.mList[0].mData.mArea); + EXPECT_EQ(result.mList[0].mData.mDuration, record.mList[0].mData.mDuration); + EXPECT_EQ(result.mList[0].mData.mMagnMin, record.mList[0].mData.mMagnMin); + EXPECT_EQ(result.mList[0].mData.mMagnMax, record.mList[0].mData.mMagnMax); } TEST_P(Esm3SaveLoadRecordTest, weaponShouldNotChange) diff --git a/components/esm3/activespells.cpp b/components/esm3/activespells.cpp index 8ce47b7719..c3d86c3ebf 100644 --- a/components/esm3/activespells.cpp +++ b/components/esm3/activespells.cpp @@ -93,11 +93,12 @@ namespace ESM { for (const auto& params : spells) { - esm.writeHNRefId(tag, params.mId); + esm.writeHNRefId(tag, params.mSourceSpellId); + esm.writeHNRefId("SPID", params.mActiveSpellId); esm.writeHNT("CAST", params.mCasterActorId); esm.writeHNString("DISP", params.mDisplayName); - esm.writeHNT("TYPE", params.mType); + esm.writeHNT("FLAG", params.mFlags); if (params.mItem.isSet()) esm.writeFormId(params.mItem, true, "ITEM"); if (params.mWorsenings >= 0) @@ -130,14 +131,42 @@ namespace ESM while (esm.isNextSub(tag)) { ActiveSpells::ActiveSpellParams params; - params.mId = esm.getRefId(); + params.mSourceSpellId = esm.getRefId(); + if (format > MaxActiveSpellTypeVersion) + params.mActiveSpellId = esm.getHNRefId("SPID"); esm.getHNT(params.mCasterActorId, "CAST"); params.mDisplayName = esm.getHNString("DISP"); if (format <= MaxClearModifiersFormatVersion) - params.mType = ActiveSpells::Type_Temporary; + params.mFlags = Compatibility::ActiveSpells::Type_Temporary_Flags; else { - esm.getHNT(params.mType, "TYPE"); + if (format <= MaxActiveSpellTypeVersion) + { + Compatibility::ActiveSpells::EffectType type; + esm.getHNT(type, "TYPE"); + switch (type) + { + case Compatibility::ActiveSpells::Type_Ability: + params.mFlags = Compatibility::ActiveSpells::Type_Ability_Flags; + break; + case Compatibility::ActiveSpells::Type_Consumable: + params.mFlags = Compatibility::ActiveSpells::Type_Consumable_Flags; + break; + case Compatibility::ActiveSpells::Type_Enchantment: + params.mFlags = Compatibility::ActiveSpells::Type_Enchantment_Flags; + break; + case Compatibility::ActiveSpells::Type_Permanent: + params.mFlags = Compatibility::ActiveSpells::Type_Permanent_Flags; + break; + case Compatibility::ActiveSpells::Type_Temporary: + params.mFlags = Compatibility::ActiveSpells::Type_Temporary_Flags; + break; + } + } + else + { + esm.getHNT(params.mFlags, "FLAG"); + } if (esm.peekNextSub("ITEM")) { if (format <= MaxActiveSpellSlotIndexFormatVersion) diff --git a/components/esm3/activespells.hpp b/components/esm3/activespells.hpp index 0e4e01eda3..daec9fc515 100644 --- a/components/esm3/activespells.hpp +++ b/components/esm3/activespells.hpp @@ -46,23 +46,28 @@ namespace ESM // format 0, saved games only struct ActiveSpells { - enum EffectType + enum Flags : uint32_t { - Type_Temporary, - Type_Ability, - Type_Enchantment, - Type_Permanent, - Type_Consumable + Flag_Temporary = 1 << 0, //!< Effect will end automatically once its duration ends. + Flag_Equipment = 1 << 1, //!< Effect will end automatically if item is unequipped. + Flag_SpellStore = 1 << 2, //!< Effect will end automatically if removed from the actor's spell store. + Flag_AffectsBaseValues = 1 << 3, //!< Effects will affect base values instead of current values. + Flag_Stackable + = 1 << 4, //!< Effect can stack. If this flag is not set, spells from the same caster and item cannot stack. + Flag_Lua + = 1 << 5, //!< Effect was added via Lua. Should not do any vfx/sound as this is handled by Lua scripts. }; struct ActiveSpellParams { - RefId mId; + RefId mActiveSpellId; + RefId mSourceSpellId; std::vector mEffects; std::string mDisplayName; int32_t mCasterActorId; RefNum mItem; - EffectType mType; + Flags mFlags; + bool mStackable; int32_t mWorsenings; TimeStamp mNextWorsening; }; @@ -73,6 +78,29 @@ namespace ESM void load(ESMReader& esm); void save(ESMWriter& esm) const; }; + + namespace Compatibility + { + namespace ActiveSpells + { + enum EffectType + { + Type_Temporary, + Type_Ability, + Type_Enchantment, + Type_Permanent, + Type_Consumable, + }; + + using Flags = ESM::ActiveSpells::Flags; + constexpr Flags Type_Temporary_Flags = Flags::Flag_Temporary; + constexpr Flags Type_Consumable_Flags = static_cast(Flags::Flag_Temporary | Flags::Flag_Stackable); + constexpr Flags Type_Permanent_Flags = Flags::Flag_SpellStore; + constexpr Flags Type_Ability_Flags + = static_cast(Flags::Flag_SpellStore | Flags::Flag_AffectsBaseValues); + constexpr Flags Type_Enchantment_Flags = Flags::Flag_Equipment; + } + } } #endif diff --git a/components/esm3/effectlist.cpp b/components/esm3/effectlist.cpp index 4f21f47fa2..a71eccfb84 100644 --- a/components/esm3/effectlist.cpp +++ b/components/esm3/effectlist.cpp @@ -22,19 +22,40 @@ namespace ESM } } + void EffectList::populate(const std::vector& effects) + { + mList.clear(); + for (size_t i = 0; i < effects.size(); i++) + mList.push_back({ effects[i], static_cast(i) }); + } + + void EffectList::updateIndexes() + { + for (size_t i = 0; i < mList.size(); i++) + mList[i].mIndex = i; + } + void EffectList::add(ESMReader& esm) { ENAMstruct s; esm.getSubComposite(s); - mList.push_back(s); + mList.push_back({ s, static_cast(mList.size()) }); } void EffectList::save(ESMWriter& esm) const { - for (const ENAMstruct& enam : mList) + for (const IndexedENAMstruct& enam : mList) { - esm.writeNamedComposite("ENAM", enam); + esm.writeNamedComposite("ENAM", enam.mData); } } + bool IndexedENAMstruct::operator!=(const IndexedENAMstruct& rhs) const + { + return mData.mEffectID != rhs.mData.mEffectID || mData.mArea != rhs.mData.mArea + || mData.mRange != rhs.mData.mRange || mData.mSkill != rhs.mData.mSkill + || mData.mAttribute != rhs.mData.mAttribute || mData.mMagnMin != rhs.mData.mMagnMin + || mData.mMagnMax != rhs.mData.mMagnMax || mData.mDuration != rhs.mData.mDuration; + } + } // end namespace diff --git a/components/esm3/effectlist.hpp b/components/esm3/effectlist.hpp index de13797496..8eb347d6c8 100644 --- a/components/esm3/effectlist.hpp +++ b/components/esm3/effectlist.hpp @@ -26,10 +26,21 @@ namespace ESM int32_t mArea, mDuration, mMagnMin, mMagnMax; }; + struct IndexedENAMstruct + { + bool operator!=(const IndexedENAMstruct& rhs) const; + bool operator==(const IndexedENAMstruct& rhs) const { return !(this->operator!=(rhs)); } + ENAMstruct mData; + uint32_t mIndex; + }; + /// EffectList, ENAM subrecord struct EffectList { - std::vector mList; + std::vector mList; + + void populate(const std::vector& effects); + void updateIndexes(); /// Load one effect, assumes subrecord name was already read void add(ESMReader& esm); diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index d90742a512..36e43728e2 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -26,7 +26,8 @@ namespace ESM inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26; inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; inline constexpr FormatVersion MaxOldCountFormatVersion = 30; - inline constexpr FormatVersion CurrentSaveGameFormatVersion = 31; + inline constexpr FormatVersion MaxActiveSpellTypeVersion = 31; + inline constexpr FormatVersion CurrentSaveGameFormatVersion = 32; inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 5; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 66fb817362..7963853e2b 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -343,6 +343,11 @@ -- @field #string id Record id of the spell or item used to cast the spell -- @field #GameObject item The enchanted item used to cast the spell, or nil if the spell was not cast from an enchanted item. Note that if the spell was cast for a single-use enchantment such as a scroll, this will be nil. -- @field #GameObject caster The caster object, or nil if the spell has no defined caster +-- @field #boolean fromEquipment If set, this spell is tied to an equipped item and can only be ended by unequipping the item. +-- @field #boolean temporary If set, this spell effect is temporary and should end on its own. Either after a single application or after its duration has run out. +-- @field #boolean affectsBaseValues If set, this spell affects the base values of affected stats, rather than modifying current values. +-- @field #boolean stackable If set, this spell can be applied multiple times. If not set, the same spell can only be applied once from the same source (where source is determined by caster + item). In vanilla rules, consumables are stackable while spells and enchantments are not. +-- @field #number activeSpellId A number uniquely identifying this active spell within the affected actor's list of active spells. -- @field #list<#ActiveSpellEffect> effects The active effects (@{#ActiveSpellEffect}) of this spell. --- @@ -373,7 +378,7 @@ -- @type Enchantment -- @field #string id Enchantment id -- @field #number type @{#EnchantmentType} --- @field #number autocalcFlag If set, the casting cost should be computer rather than reading the cost field +-- @field #boolean autocalcFlag If set, the casting cost should be computed based on the effect list rather than read from the cost field -- @field #number cost -- @field #number charge Charge capacity. Should not be confused with current charge. -- @field #list<#MagicEffectWithParams> effects The effects (@{#MagicEffectWithParams}) of the enchantment @@ -664,6 +669,8 @@ -- @field #number type @{#SpellType} -- @field #number cost -- @field #list<#MagicEffectWithParams> effects The effects (@{#MagicEffectWithParams}) of the spell +-- @field #boolean alwaysSucceedFlag If set, the spell should ignore skill checks and always succeed. +-- @field #boolean autocalcFlag If set, the casting cost should be computed based on the effect list rather than read from the cost field --- -- @type MagicEffect @@ -673,16 +680,28 @@ -- @field #string school Skill ID that is this effect's school -- @field #number baseCost -- @field openmw.util#Color color --- @field #boolean harmful +-- @field #boolean harmful If set, the effect is considered harmful and should elicit a hostile reaction from affected NPCs. -- @field #boolean continuousVfx Whether the magic effect's vfx should loop or not +-- @field #boolean hasDuration If set, the magic effect has a duration. As an example, divine intervention has no duration while fire damage does. +-- @field #boolean hasMagnitude If set, the magic effect depends on a magnitude. As an example, cure common disease has no magnitude while chameleon does. +-- @field #boolean isAppliedOnce If set, the magic effect is applied fully on cast, rather than being continuously applied over the effect's duration. For example, chameleon is applied once, while fire damage is continuously applied for the duration. +-- @field #boolean casterLinked If set, it is implied the magic effect links back to the caster in some way and should end immediately or never be applied if the caster dies or is not an actor. +-- @field #boolean nonRecastable If set, this effect cannot be re-applied until it has ended. This is used by bound equipment spells. -- @field #string particle Identifier of the particle texture --- @field #string castingStatic Identifier of the vfx static used for casting +-- @field #string castStatic Identifier of the vfx static used for casting -- @field #string hitStatic Identifier of the vfx static used on hit -- @field #string areaStatic Identifier of the vfx static used for AOE spells +-- @field #string boltStatic Identifier of the projectile vfx static used for ranged spells +-- @field #string castSound Identifier of the sound used for casting +-- @field #string hitSound Identifier of the sound used on hit +-- @field #string areaSound Identifier of the sound used for AOE spells +-- @field #string boltSound Identifier of the projectile sound used for ranged spells + --- -- @type MagicEffectWithParams -- @field #MagicEffect effect @{#MagicEffect} +-- @field #string id ID of the associated @{#MagicEffect} -- @field #string affectedSkill Optional skill ID -- @field #string affectedAttribute Optional attribute ID -- @field #number range @@ -690,6 +709,7 @@ -- @field #number magnitudeMin -- @field #number magnitudeMax -- @field #number duration +-- @field #number index Index of this effect within the original list of @{#MagicEffectWithParams} of the spell/enchantment/potion this effect came from. --- -- @type ActiveEffect @@ -701,6 +721,7 @@ -- @field #number magnitude current magnitude of the effect. Will be set to 0 when effect is removed or expires. -- @field #number magnitudeBase -- @field #number magnitudeModifier +-- @field #number index Index of this effect within the original list of @{#MagicEffectWithParams} of the spell/enchantment/potion this effect came from. --- @{#Sound}: Sounds and Speech -- @field [parent=#core] #Sound sound diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 60f3e79628..64dab547f1 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -302,17 +302,59 @@ -- end --- --- Get whether a specific spell is active on the actor. +-- Get whether any instance of the specific spell is active on the actor. -- @function [parent=#ActorActiveSpells] isSpellActive -- @param self --- @param #any recordOrId record or string record ID of the active spell's source. valid records are @{openmw.core#Spell}, @{openmw.core#Enchantment}, #IngredientRecord, or #PotionRecord +-- @param #any recordOrId A record or string record ID. Valid records are @{openmw.core#Spell}, enchanted @{#Item}, @{#IngredientRecord}, or @{#PotionRecord}. -- @return true if spell is active, false otherwise --- --- Remove the given spell and all its effects from the given actor's active spells. +-- If true, the actor has not used this power in the last 24h. Will return true for powers the actor does not have. +-- @function [parent=#ActorActiveSpells] canUsePower +-- @param self +-- @param #any spellOrId A @{openmw.core#Spell} or string record id. + +--- +-- Remove an active spell based on active spell ID (see @{openmw.core#ActiveSpell.activeSpellId}). Can only be used in global scripts or on self. Can only be used to remove spells with the temporary flag set (see @{openmw.core#ActiveSpell.temporary}). -- @function [parent=#ActorActiveSpells] remove -- @param self --- @param #any spellOrId @{openmw.core#Spell} or string spell id +-- @param #any id Active spell ID. + +--- +-- Adds a new spell to the list of active spells (only in global scripts or on self). +-- Note that this does not play any related VFX or sounds. +-- @function [parent=#ActorActiveSpells] add +-- @param self +-- @param #table options A table of parameters. Must contain the following required parameters: +-- +-- * `id` - A string record ID. Valid records are @{openmw.core#Spell}, enchanted @{#Item}, @{#IngredientRecord}, or @{#PotionRecord}. +-- * `effects` - A list of indexes of the effects to be applied. These indexes must be in range of the record's list of @{openmw.core#MagicEffectWithParams}. Note that for Ingredients, normal ingredient consumption rules will be applied to effects. +-- +-- And may contain the following optional parameters: +-- +-- * `name` - The name to show in the list of active effects in the UI. Default: Name of the record identified by the id. +-- * `ignoreResistances` - If true, resistances will be ignored. Default: false +-- * `ignoreSpellAbsorption` - If true, spell absorption will not be applied. Default: false. +-- * `ignoreReflect` - If true, reflects will not be applied. Default: false. +-- * `caster` - A game object that identifies the caster. Default: nil +-- * `item` - A game object that identifies the specific enchanted item instance used to cast the spell. Default: nil +-- * `stackable` - If true, the spell will be able to stack. If false, existing instances of spells with the same id from the same source (where source is caster + item) +-- * `quiet` - If true, no messages will be printed if the spell is an Ingredient and it had no effect. Always true if the target is not the player. +-- @usage +-- -- Adds the effect of the chameleon spell to the character +-- Actor.activeSpells(self):add({id = 'chameleon', effects = { 0 }}) +-- @usage +-- -- Adds the effect of a standard potion of intelligence, without consuming any potions from the character's inventory. +-- -- Note that stackable = true to let the effect stack like a potion should. +-- Actor.activeSpells(self):add({id = 'p_fortify_intelligence_s', effects = { 0 }, stackable = true}) +-- @usage +-- -- Adds the negative effect of Greef twice over, and renames it to Good Greef. +-- Actor.activeSpells(self):add({id = 'potion_comberry_brandy_01', effects = { 1, 1 }, stackable = true, name = 'Good Greef'}) +-- @usage +-- -- Has the same effect as if the actor ate a chokeweed. With the same variable effect based on skill / random chance. +-- Actor.activeSpells(self):add({id = 'ingred_chokeweed_01', effects = { 0 }, stackable = true, name = 'Chokeweed'}) +-- -- Same as above, but uses a different index. Note that if multiple indexes are used, the randomicity is applied separately for each effect. +-- Actor.activeSpells(self):add({id = 'ingred_chokeweed_01', effects = { 1 }, stackable = true, name = 'Chokeweed'}) --- -- Return the spells (@{#ActorSpells}) of the given actor. From 7897ff7ac9fa3f1f1acc0c7dd91f2f2ba22fb6ff Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Mon, 25 Mar 2024 21:01:46 +0000 Subject: [PATCH 308/451] Fix weapon sheathing for non-nif meshes --- apps/openmw/mwrender/actoranimation.cpp | 11 ++++------- apps/openmw/mwrender/actorutil.cpp | 12 ++++++++++++ apps/openmw/mwrender/actorutil.hpp | 1 + apps/openmw/mwrender/npcanimation.cpp | 3 +-- components/sceneutil/visitor.cpp | 25 +++++++++++++++++++++++++ 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 7da29a1cf0..6e18fb51a1 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -35,6 +35,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/ptr.hpp" +#include "actorutil.hpp" #include "vismask.hpp" namespace MWRender @@ -144,8 +145,7 @@ namespace MWRender if (mesh.empty()) return mesh; - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + const std::string holsteredName = addSuffixBeforeExtension(mesh, "_sh"); if (mResourceSystem->getVFS()->exists(holsteredName)) { osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); @@ -222,8 +222,7 @@ namespace MWRender std::string_view boneName = "Bip01 AttachShield"; osg::Vec4f glowColor = shield->getClass().getEnchantmentColor(*shield); - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + const std::string holsteredName = addSuffixBeforeExtension(mesh, "_sh"); bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty(); // If we have no dedicated sheath model, use basic shield model as fallback. @@ -340,14 +339,12 @@ namespace MWRender showHolsteredWeapons = false; std::string mesh = weapon->getClass().getCorrectedModel(*weapon); - std::string scabbardName = mesh; - std::string_view boneName = getHolsteredWeaponBoneName(*weapon); if (mesh.empty() || boneName.empty()) return; // If the scabbard is not found, use the weapon mesh as fallback. - scabbardName = scabbardName.replace(scabbardName.size() - 4, 4, "_sh.nif"); + const std::string scabbardName = addSuffixBeforeExtension(mesh, "_sh"); bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); if (!mResourceSystem->getVFS()->exists(scabbardName)) { diff --git a/apps/openmw/mwrender/actorutil.cpp b/apps/openmw/mwrender/actorutil.cpp index 8da921e532..7739d5a6f6 100644 --- a/apps/openmw/mwrender/actorutil.cpp +++ b/apps/openmw/mwrender/actorutil.cpp @@ -37,4 +37,16 @@ namespace MWRender || VFS::Path::pathEqual(Settings::models().mBaseanimfemale.get(), model) || VFS::Path::pathEqual(Settings::models().mBaseanim.get(), model); } + + std::string addSuffixBeforeExtension(const std::string& filename, const std::string& suffix) + { + size_t dotPos = filename.rfind('.'); + + // No extension found; return the original filename with suffix appended + if (dotPos == std::string::npos) + return filename + suffix; + + // Insert the suffix before the dot (extension) and return the new filename + return filename.substr(0, dotPos) + suffix + filename.substr(dotPos); + } } diff --git a/apps/openmw/mwrender/actorutil.hpp b/apps/openmw/mwrender/actorutil.hpp index 3107bf0183..6a5ab12dea 100644 --- a/apps/openmw/mwrender/actorutil.hpp +++ b/apps/openmw/mwrender/actorutil.hpp @@ -8,6 +8,7 @@ namespace MWRender { const std::string& getActorSkeleton(bool firstPerson, bool female, bool beast, bool werewolf); bool isDefaultActorSkeleton(std::string_view model); + std::string addSuffixBeforeExtension(const std::string& filename, const std::string& suffix); } #endif diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index b9ad471bf5..721806d992 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -539,8 +539,7 @@ namespace MWRender if (mesh.empty()) return std::string(); - std::string holsteredName = mesh; - holsteredName = holsteredName.replace(holsteredName.size() - 4, 4, "_sh.nif"); + const std::string holsteredName = addSuffixBeforeExtension(mesh, "_sh"); if (mResourceSystem->getVFS()->exists(holsteredName)) { osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index aadbf35af8..ffcf12b167 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -21,13 +21,36 @@ namespace SceneUtil mFoundNode = &group; return true; } + + // FIXME: can the nodes/bones be renamed at loading stage rather than each time? + // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses + // whitespace-separated names) + std::string nodeName = group.getName(); + std::replace(nodeName.begin(), nodeName.end(), '_', ' '); + if (Misc::StringUtils::ciEqual(nodeName, mNameToFind)) + { + mFoundNode = &group; + return true; + } return false; } void FindByClassVisitor::apply(osg::Node& node) { if (Misc::StringUtils::ciEqual(node.className(), mNameToFind)) + { mFoundNodes.push_back(&node); + } + else + { + // FIXME: can the nodes/bones be renamed at loading stage rather than each time? + // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses + // whitespace-separated names) + std::string nodeName = node.className(); + std::replace(nodeName.begin(), nodeName.end(), '_', ' '); + if (Misc::StringUtils::ciEqual(nodeName, mNameToFind)) + mFoundNodes.push_back(&node); + } traverse(node); } @@ -53,6 +76,8 @@ namespace SceneUtil if (trans.libraryName() == std::string_view("osgAnimation")) { std::string nodeName = trans.getName(); + + // FIXME: can the nodes/bones be renamed at loading stage rather than each time? // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses // whitespace-separated names) std::replace(nodeName.begin(), nodeName.end(), '_', ' '); From e0b11c14c28b30b810047fd7efddfcdfd652d429 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 26 Mar 2024 14:44:02 +0000 Subject: [PATCH 309/451] Remove unused member mStackable --- components/esm3/activespells.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/esm3/activespells.hpp b/components/esm3/activespells.hpp index daec9fc515..19e1f53be5 100644 --- a/components/esm3/activespells.hpp +++ b/components/esm3/activespells.hpp @@ -67,7 +67,6 @@ namespace ESM int32_t mCasterActorId; RefNum mItem; Flags mFlags; - bool mStackable; int32_t mWorsenings; TimeStamp mNextWorsening; }; From 59334f694dbcac004e58a271f92729371861dec3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 26 Mar 2024 23:11:54 +0000 Subject: [PATCH 310/451] Don't forget to add path to UserRole --- apps/launcher/datafilespage.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 87667bda37..8e0d729afa 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -712,6 +712,9 @@ void Launcher::DataFilesPage::addSubdirectories(bool append) if (!ui.directoryListWidget->findItems(rootPath, Qt::MatchFixedString).isEmpty()) return; ui.directoryListWidget->addItem(rootPath); + auto row = ui.directoryListWidget->count() - 1; + auto* item = ui.directoryListWidget->item(row); + item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue(rootPath))); mNewDataDirs.push_back(rootPath); refreshDataFilesView(); return; @@ -741,8 +744,11 @@ void Launcher::DataFilesPage::addSubdirectories(bool append) const auto* dir = select.dirListWidget->item(i); if (dir->checkState() == Qt::Checked) { - ui.directoryListWidget->insertItem(selectedRow++, dir->text()); + ui.directoryListWidget->insertItem(selectedRow, dir->text()); + auto* item = ui.directoryListWidget->item(selectedRow); + item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue(dir->text()))); mNewDataDirs.push_back(dir->text()); + ++selectedRow; } } From 2e6878633145a7dc6b6897a1110b9fb4817ce52e Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 27 Mar 2024 07:32:53 +0000 Subject: [PATCH 311/451] Fix(CS): Actually allow unlocking doors ( #7899 ) --- CHANGELOG.md | 1 + apps/opencs/model/world/columnimp.hpp | 31 +++++++++++++++++++++++++-- apps/opencs/model/world/columns.cpp | 1 + apps/opencs/model/world/columns.hpp | 2 ++ apps/opencs/model/world/data.cpp | 2 ++ components/esm3/cellref.cpp | 12 ++++++----- 6 files changed, 42 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af3daecddc..e82ca284d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -163,6 +163,7 @@ Bug #7872: Region sounds use wrong odds Bug #7887: Editor: Mismatched reported script data size and actual data size causes a crash during save Bug #7898: Editor: Invalid reference scales are allowed + Bug #7899: Editor: Doors can't be unlocked Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 53e0ba07cf..cb263eb8bc 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1182,6 +1182,26 @@ namespace CSMWorld bool isUserEditable() const override { return true; } }; + template + struct IsLockedColumn : public Column + { + IsLockedColumn(int flags) + : Column(Columns::ColumnId_IsLocked, ColumnBase::Display_Boolean, flags) + { + } + + QVariant get(const Record& record) const override { return record.get().mIsLocked; } + + void set(Record& record, const QVariant& data) override + { + ESXRecordT record2 = record.get(); + record2.mIsLocked = data.toBool(); + record.setModified(record2); + } + + bool isEditable() const override { return true; } + }; + template struct LockLevelColumn : public Column { @@ -1190,7 +1210,12 @@ namespace CSMWorld { } - QVariant get(const Record& record) const override { return record.get().mLockLevel; } + QVariant get(const Record& record) const override + { + if (record.get().mIsLocked) + return record.get().mLockLevel; + return QVariant(); + } void set(Record& record, const QVariant& data) override { @@ -1212,7 +1237,9 @@ namespace CSMWorld QVariant get(const Record& record) const override { - return QString::fromUtf8(record.get().mKey.getRefIdString().c_str()); + if (record.get().mIsLocked) + return QString::fromUtf8(record.get().mKey.getRefIdString().c_str()); + return QVariant(); } void set(Record& record, const QVariant& data) override diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index f487266dbb..0f42b12508 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -59,6 +59,7 @@ namespace CSMWorld { ColumnId_StackCount, "Count" }, { ColumnId_Teleport, "Teleport" }, { ColumnId_TeleportCell, "Teleport Cell" }, + { ColumnId_IsLocked, "Locked" }, { ColumnId_LockLevel, "Lock Level" }, { ColumnId_Key, "Key" }, { ColumnId_Trap, "Trap" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index f5a8e446a5..a691eada9e 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -351,6 +351,8 @@ namespace CSMWorld ColumnId_SoundProbability = 317, + ColumnId_IsLocked = 318, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 1f8ff54e89..1bff00701a 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -599,6 +599,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mRefs.addColumn(new RotColumn(&CellRef::mDoorDest, 0, true)); mRefs.addColumn(new RotColumn(&CellRef::mDoorDest, 1, true)); mRefs.addColumn(new RotColumn(&CellRef::mDoorDest, 2, true)); + mRefs.addColumn(new IsLockedColumn( + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mRefs.addColumn(new LockLevelColumn); mRefs.addColumn(new KeyColumn); mRefs.addColumn(new TrapColumn); diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index ecba6f7f5e..97ccfb730a 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -230,12 +230,14 @@ namespace ESM if (!inInventory) { - int lockLevel = mLockLevel; - if (lockLevel == 0 && mIsLocked) - lockLevel = ZeroLock; - if (lockLevel != 0) + if (mIsLocked) + { + int lockLevel = mLockLevel; + if (lockLevel == 0) + lockLevel = ZeroLock; esm.writeHNT("FLTV", lockLevel); - esm.writeHNOCRefId("KNAM", mKey); + esm.writeHNOCRefId("KNAM", mKey); + } esm.writeHNOCRefId("TNAM", mTrap); } From f2dc25e214ad49b215f1cec9326a6bc3fe2ce951 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 27 Mar 2024 12:44:35 +0400 Subject: [PATCH 312/451] Optimize bitmap fonts loading --- components/fontloader/fontloader.cpp | 120 +++++++++++++-------------- 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 64aa32310b..c84392c421 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -448,6 +448,63 @@ namespace Gui source->addAttribute("value", bitmapFilename); MyGUI::xml::ElementPtr codes = root->createChild("Codes"); + // Fall back from unavailable Windows-1252 encoding symbols to similar characters available in the game + // fonts + std::multimap additional; // fallback glyph index, unicode + additional.emplace(156, 0x00A2); // cent sign + additional.emplace(89, 0x00A5); // yen sign + additional.emplace(221, 0x00A6); // broken bar + additional.emplace(99, 0x00A9); // copyright sign + additional.emplace(97, 0x00AA); // prima ordinal indicator + additional.emplace(60, 0x00AB); // double left-pointing angle quotation mark + additional.emplace(45, 0x00AD); // soft hyphen + additional.emplace(114, 0x00AE); // registered trademark symbol + additional.emplace(45, 0x00AF); // macron + additional.emplace(241, 0x00B1); // plus-minus sign + additional.emplace(50, 0x00B2); // superscript two + additional.emplace(51, 0x00B3); // superscript three + additional.emplace(44, 0x00B8); // cedilla + additional.emplace(49, 0x00B9); // superscript one + additional.emplace(111, 0x00BA); // primo ordinal indicator + additional.emplace(62, 0x00BB); // double right-pointing angle quotation mark + additional.emplace(63, 0x00BF); // inverted question mark + additional.emplace(65, 0x00C6); // latin capital ae ligature + additional.emplace(79, 0x00D8); // latin capital o with stroke + additional.emplace(97, 0x00E6); // latin small ae ligature + additional.emplace(111, 0x00F8); // latin small o with stroke + additional.emplace(79, 0x0152); // latin capital oe ligature + additional.emplace(111, 0x0153); // latin small oe ligature + additional.emplace(83, 0x015A); // latin capital s with caron + additional.emplace(115, 0x015B); // latin small s with caron + additional.emplace(89, 0x0178); // latin capital y with diaresis + additional.emplace(90, 0x017D); // latin capital z with caron + additional.emplace(122, 0x017E); // latin small z with caron + additional.emplace(102, 0x0192); // latin small f with hook + additional.emplace(94, 0x02C6); // circumflex modifier + additional.emplace(126, 0x02DC); // small tilde + additional.emplace(69, 0x0401); // cyrillic capital io (no diaeresis latin e is available) + additional.emplace(137, 0x0451); // cyrillic small io + additional.emplace(45, 0x2012); // figure dash + additional.emplace(45, 0x2013); // en dash + additional.emplace(45, 0x2014); // em dash + additional.emplace(39, 0x2018); // left single quotation mark + additional.emplace(39, 0x2019); // right single quotation mark + additional.emplace(44, 0x201A); // single low quotation mark + additional.emplace(39, 0x201B); // single high quotation mark (reversed) + additional.emplace(34, 0x201C); // left double quotation mark + additional.emplace(34, 0x201D); // right double quotation mark + additional.emplace(44, 0x201E); // double low quotation mark + additional.emplace(34, 0x201F); // double high quotation mark (reversed) + additional.emplace(43, 0x2020); // dagger + additional.emplace(216, 0x2021); // double dagger (note: this glyph is not available) + additional.emplace(46, 0x2026); // ellipsis + additional.emplace(37, 0x2030); // per mille sign + additional.emplace(60, 0x2039); // single left-pointing angle quotation mark + additional.emplace(62, 0x203A); // single right-pointing angle quotation mark + additional.emplace(101, 0x20AC); // euro sign + additional.emplace(84, 0x2122); // trademark sign + additional.emplace(45, 0x2212); // minus sign + for (int i = 0; i < 256; i++) { float x1 = data[i].top_left.x * width; @@ -470,69 +527,10 @@ namespace Gui code->addAttribute( "size", MyGUI::IntSize(static_cast(data[i].width), static_cast(data[i].height))); - // Fall back from unavailable Windows-1252 encoding symbols to similar characters available in the game - // fonts - std::multimap additional; // fallback glyph index, unicode - additional.insert(std::make_pair(156, 0x00A2)); // cent sign - additional.insert(std::make_pair(89, 0x00A5)); // yen sign - additional.insert(std::make_pair(221, 0x00A6)); // broken bar - additional.insert(std::make_pair(99, 0x00A9)); // copyright sign - additional.insert(std::make_pair(97, 0x00AA)); // prima ordinal indicator - additional.insert(std::make_pair(60, 0x00AB)); // double left-pointing angle quotation mark - additional.insert(std::make_pair(45, 0x00AD)); // soft hyphen - additional.insert(std::make_pair(114, 0x00AE)); // registered trademark symbol - additional.insert(std::make_pair(45, 0x00AF)); // macron - additional.insert(std::make_pair(241, 0x00B1)); // plus-minus sign - additional.insert(std::make_pair(50, 0x00B2)); // superscript two - additional.insert(std::make_pair(51, 0x00B3)); // superscript three - additional.insert(std::make_pair(44, 0x00B8)); // cedilla - additional.insert(std::make_pair(49, 0x00B9)); // superscript one - additional.insert(std::make_pair(111, 0x00BA)); // primo ordinal indicator - additional.insert(std::make_pair(62, 0x00BB)); // double right-pointing angle quotation mark - additional.insert(std::make_pair(63, 0x00BF)); // inverted question mark - additional.insert(std::make_pair(65, 0x00C6)); // latin capital ae ligature - additional.insert(std::make_pair(79, 0x00D8)); // latin capital o with stroke - additional.insert(std::make_pair(97, 0x00E6)); // latin small ae ligature - additional.insert(std::make_pair(111, 0x00F8)); // latin small o with stroke - additional.insert(std::make_pair(79, 0x0152)); // latin capital oe ligature - additional.insert(std::make_pair(111, 0x0153)); // latin small oe ligature - additional.insert(std::make_pair(83, 0x015A)); // latin capital s with caron - additional.insert(std::make_pair(115, 0x015B)); // latin small s with caron - additional.insert(std::make_pair(89, 0x0178)); // latin capital y with diaresis - additional.insert(std::make_pair(90, 0x017D)); // latin capital z with caron - additional.insert(std::make_pair(122, 0x017E)); // latin small z with caron - additional.insert(std::make_pair(102, 0x0192)); // latin small f with hook - additional.insert(std::make_pair(94, 0x02C6)); // circumflex modifier - additional.insert(std::make_pair(126, 0x02DC)); // small tilde - additional.insert(std::make_pair(69, 0x0401)); // cyrillic capital io (no diaeresis latin e is available) - additional.insert(std::make_pair(137, 0x0451)); // cyrillic small io - additional.insert(std::make_pair(45, 0x2012)); // figure dash - additional.insert(std::make_pair(45, 0x2013)); // en dash - additional.insert(std::make_pair(45, 0x2014)); // em dash - additional.insert(std::make_pair(39, 0x2018)); // left single quotation mark - additional.insert(std::make_pair(39, 0x2019)); // right single quotation mark - additional.insert(std::make_pair(44, 0x201A)); // single low quotation mark - additional.insert(std::make_pair(39, 0x201B)); // single high quotation mark (reversed) - additional.insert(std::make_pair(34, 0x201C)); // left double quotation mark - additional.insert(std::make_pair(34, 0x201D)); // right double quotation mark - additional.insert(std::make_pair(44, 0x201E)); // double low quotation mark - additional.insert(std::make_pair(34, 0x201F)); // double high quotation mark (reversed) - additional.insert(std::make_pair(43, 0x2020)); // dagger - additional.insert(std::make_pair(216, 0x2021)); // double dagger (note: this glyph is not available) - additional.insert(std::make_pair(46, 0x2026)); // ellipsis - additional.insert(std::make_pair(37, 0x2030)); // per mille sign - additional.insert(std::make_pair(60, 0x2039)); // single left-pointing angle quotation mark - additional.insert(std::make_pair(62, 0x203A)); // single right-pointing angle quotation mark - additional.insert(std::make_pair(101, 0x20AC)); // euro sign - additional.insert(std::make_pair(84, 0x2122)); // trademark sign - additional.insert(std::make_pair(45, 0x2212)); // minus sign - - for (const auto& [key, value] : additional) + for (auto [it, end] = additional.equal_range(i); it != end; ++it) { - if (key != i) - continue; code = codes->createChild("Code"); - code->addAttribute("index", value); + code->addAttribute("index", it->second); code->addAttribute("coord", MyGUI::utility::toString(x1) + " " + MyGUI::utility::toString(y1) + " " + MyGUI::utility::toString(w) + " " + MyGUI::utility::toString(h)); From e87c39eeb3db26836300b20ab6a5d2fc5dc83b05 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 10 Mar 2024 03:23:24 +0000 Subject: [PATCH 313/451] OpenCS: Editing and verifying of projectile speed for magic effects --- apps/opencs/model/tools/magiceffectcheck.cpp | 5 +++++ apps/opencs/model/world/columnimp.hpp | 20 ++++++++++++++++++++ apps/opencs/model/world/columns.cpp | 1 + apps/opencs/model/world/columns.hpp | 2 ++ apps/opencs/model/world/data.cpp | 2 ++ 5 files changed, 30 insertions(+) diff --git a/apps/opencs/model/tools/magiceffectcheck.cpp b/apps/opencs/model/tools/magiceffectcheck.cpp index e44119bb67..212b343e00 100644 --- a/apps/opencs/model/tools/magiceffectcheck.cpp +++ b/apps/opencs/model/tools/magiceffectcheck.cpp @@ -60,6 +60,11 @@ void CSMTools::MagicEffectCheckStage::perform(int stage, CSMDoc::Messages& messa ESM::MagicEffect effect = record.get(); CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_MagicEffect, CSMWorld::getRecordId(effect)); + if (effect.mData.mSpeed <= 0.0f) + { + messages.add(id, "Speed is less than or equal to zero", "", CSMDoc::Message::Severity_Error); + } + if (effect.mDescription.empty()) { messages.add(id, "Description is missing", "", CSMDoc::Message::Severity_Warning); diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index cb263eb8bc..b5205da27c 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -2079,6 +2079,26 @@ namespace CSMWorld bool isEditable() const override { return true; } }; + template + struct ProjectileSpeedColumn : public Column + { + ProjectileSpeedColumn() + : Column(Columns::ColumnId_ProjectileSpeed, ColumnBase::Display_Float) + { + } + + QVariant get(const Record& record) const override { return record.get().mData.mSpeed; } + + void set(Record& record, const QVariant& data) override + { + ESXRecordT record2 = record.get(); + record2.mData.mSpeed = data.toFloat(); + record.setModified(record2); + } + + bool isEditable() const override { return true; } + }; + template struct SchoolColumn : public Column { diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 0f42b12508..570e4134c1 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -378,6 +378,7 @@ namespace CSMWorld { ColumnId_Blocked, "Blocked" }, { ColumnId_LevelledCreatureId, "Levelled Creature" }, + { ColumnId_ProjectileSpeed, "Projectile Speed" }, // end marker { -1, 0 }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index a691eada9e..066f6395aa 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -353,6 +353,8 @@ namespace CSMWorld ColumnId_IsLocked = 318, + ColumnId_ProjectileSpeed = 319, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 1bff00701a..bd699b12e0 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -502,6 +502,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mMagicEffects.addColumn(new FixedRecordTypeColumn(UniversalId::Type_MagicEffect)); mMagicEffects.addColumn(new SchoolColumn); mMagicEffects.addColumn(new BaseCostColumn); + mMagicEffects.addColumn(new ProjectileSpeedColumn); mMagicEffects.addColumn(new EffectTextureColumn(Columns::ColumnId_Icon)); mMagicEffects.addColumn(new EffectTextureColumn(Columns::ColumnId_Particle)); mMagicEffects.addColumn(new EffectObjectColumn(Columns::ColumnId_CastingObject)); @@ -512,6 +513,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mMagicEffects.addColumn(new EffectSoundColumn(Columns::ColumnId_HitSound)); mMagicEffects.addColumn(new EffectSoundColumn(Columns::ColumnId_AreaSound)); mMagicEffects.addColumn(new EffectSoundColumn(Columns::ColumnId_BoltSound)); + mMagicEffects.addColumn( new FlagColumn(Columns::ColumnId_AllowSpellmaking, ESM::MagicEffect::AllowSpellmaking)); mMagicEffects.addColumn( From deb889403578a499d50a9150e5eca2cbd1d5d5d6 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 17 Mar 2024 20:06:23 +0000 Subject: [PATCH 314/451] ESM::MagicEffect::blank() set the default to 1 Signed-off-by: Sam Hellawell --- components/esm3/loadmgef.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm3/loadmgef.cpp b/components/esm3/loadmgef.cpp index 8d5b99b0c3..357dd94413 100644 --- a/components/esm3/loadmgef.cpp +++ b/components/esm3/loadmgef.cpp @@ -588,7 +588,7 @@ namespace ESM mData.mRed = 0; mData.mGreen = 0; mData.mBlue = 0; - mData.mSpeed = 0; + mData.mSpeed = 1; mIcon.clear(); mParticle.clear(); From a98a824f80ce53d26a7c11a7d7e8b1a13b2283ab Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 27 Mar 2024 13:58:36 +0000 Subject: [PATCH 315/451] Config paths to info log, not verbose --- apps/launcher/maindialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 5424d4010b..5486251731 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -292,7 +292,7 @@ bool Launcher::MainDialog::setupLauncherSettings() if (!QFile::exists(path)) return true; - Log(Debug::Verbose) << "Loading config file: " << path.toUtf8().constData(); + Log(Debug::Info) << "Loading config file: " << path.toUtf8().constData(); QFile file(path); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) @@ -351,7 +351,7 @@ bool Launcher::MainDialog::setupGameSettings() for (const auto& path : Files::getActiveConfigPathsQString(mCfgMgr)) { - Log(Debug::Verbose) << "Loading config file: " << path.toUtf8().constData(); + Log(Debug::Info) << "Loading config file: " << path.toUtf8().constData(); if (!loadFile(path, &Config::GameSettings::readFile)) return false; } From e735bf67e198fcbfb74ae378b7a2e65d0e9270e9 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 27 Mar 2024 14:04:08 +0000 Subject: [PATCH 316/451] Brace-initialise SettingValue Clang didn't like it otherwise --- apps/launcher/datafilespage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 8e0d729afa..9c04be0e7e 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -714,7 +714,7 @@ void Launcher::DataFilesPage::addSubdirectories(bool append) ui.directoryListWidget->addItem(rootPath); auto row = ui.directoryListWidget->count() - 1; auto* item = ui.directoryListWidget->item(row); - item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue(rootPath))); + item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ rootPath })); mNewDataDirs.push_back(rootPath); refreshDataFilesView(); return; @@ -746,7 +746,7 @@ void Launcher::DataFilesPage::addSubdirectories(bool append) { ui.directoryListWidget->insertItem(selectedRow, dir->text()); auto* item = ui.directoryListWidget->item(selectedRow); - item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue(dir->text()))); + item->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ dir->text() })); mNewDataDirs.push_back(dir->text()); ++selectedRow; } From 47ef2d018fdcc607fb627764df04490aec191aaf Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 27 Mar 2024 22:25:32 +0000 Subject: [PATCH 317/451] Always set userrole for archive list --- apps/launcher/datafilespage.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 9c04be0e7e..f671089bff 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -867,9 +867,8 @@ bool Launcher::DataFilesPage::moveArchive(QListWidgetItem* listItem, int step) if (selectedRow == -1 || newRow < 0 || newRow > ui.archiveListWidget->count() - 1) return false; - const QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow); - - addArchive(item->text(), item->checkState(), newRow); + QListWidgetItem* item = ui.archiveListWidget->takeItem(selectedRow); + ui.archiveListWidget->insertItem(newRow, item); ui.archiveListWidget->setCurrentRow(newRow); return true; } @@ -880,6 +879,7 @@ void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState sel row = ui.archiveListWidget->count(); ui.archiveListWidget->insertItem(row, name); ui.archiveListWidget->item(row)->setCheckState(selected); + ui.archiveListWidget->item(row)->setData(Qt::UserRole, QVariant::fromValue(Config::SettingValue{ name })); if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ??? { auto item = ui.archiveListWidget->item(row); From 1360eeb839c67aaed6c787e2406e5d3fb29fbb6c Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Mon, 25 Mar 2024 16:55:19 -0500 Subject: [PATCH 318/451] Fix #7901, make teleport fields non-interactive when mTeleport is false --- CHANGELOG.md | 1 + apps/opencs/model/world/columnimp.hpp | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e82ca284d5..f797a98dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -164,6 +164,7 @@ Bug #7887: Editor: Mismatched reported script data size and actual data size causes a crash during save Bug #7898: Editor: Invalid reference scales are allowed Bug #7899: Editor: Doors can't be unlocked + Bug #7901: Editor: Teleport-related fields shouldn't be editable if a ref does not teleport Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index cb263eb8bc..9e376a5ccf 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1137,7 +1137,8 @@ namespace CSMWorld struct TeleportColumn : public Column { TeleportColumn() - : Column(Columns::ColumnId_Teleport, ColumnBase::Display_Boolean) + : Column(Columns::ColumnId_Teleport, ColumnBase::Display_Boolean, + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh) { } @@ -1165,6 +1166,8 @@ namespace CSMWorld QVariant get(const Record& record) const override { + if (!record.get().mTeleport) + return QVariant(QVariant::UserType); return QString::fromUtf8(record.get().mDestCell.c_str()); } @@ -1320,6 +1323,10 @@ namespace CSMWorld QVariant get(const Record& record) const override { + int column = this->mColumnId; + if (!record.get().mTeleport && column >= Columns::ColumnId_DoorPositionXPos + && column <= Columns::ColumnId_DoorPositionZPos) + return QVariant(QVariant::UserType); const ESM::Position& position = record.get().*mPosition; return position.pos[mIndex]; } @@ -1354,6 +1361,10 @@ namespace CSMWorld QVariant get(const Record& record) const override { + int column = this->mColumnId; + if (!record.get().mTeleport && column >= Columns::ColumnId_DoorPositionXRot + && column <= Columns::ColumnId::ColumnId_DoorPositionZRot) + return QVariant(QVariant::UserType); const ESM::Position& position = record.get().*mPosition; return osg::RadiansToDegrees(position.rot[mIndex]); } From 4e59246d2d2ca592a0247796ed8185e8fd7b5f4f Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 26 Mar 2024 01:53:25 -0500 Subject: [PATCH 319/451] Fix(columnimp.hpp): Use QVariant() constructor instead of UserType to hide unused subs from view and make a member variable to tell if the column is used for a door or a regular position --- apps/opencs/model/world/columnimp.hpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 9e376a5ccf..b1c24a0c22 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1167,7 +1167,7 @@ namespace CSMWorld QVariant get(const Record& record) const override { if (!record.get().mTeleport) - return QVariant(QVariant::UserType); + return QVariant(); return QString::fromUtf8(record.get().mDestCell.c_str()); } @@ -1312,21 +1312,21 @@ namespace CSMWorld { ESM::Position ESXRecordT::*mPosition; int mIndex; + bool mIsDoor; PosColumn(ESM::Position ESXRecordT::*position, int index, bool door) : Column((door ? Columns::ColumnId_DoorPositionXPos : Columns::ColumnId_PositionXPos) + index, ColumnBase::Display_Float) , mPosition(position) , mIndex(index) + , mIsDoor(door) { } QVariant get(const Record& record) const override { - int column = this->mColumnId; - if (!record.get().mTeleport && column >= Columns::ColumnId_DoorPositionXPos - && column <= Columns::ColumnId_DoorPositionZPos) - return QVariant(QVariant::UserType); + if (!record.get().mTeleport && mIsDoor) + return QVariant(); const ESM::Position& position = record.get().*mPosition; return position.pos[mIndex]; } @@ -1350,21 +1350,21 @@ namespace CSMWorld { ESM::Position ESXRecordT::*mPosition; int mIndex; + bool mIsDoor; RotColumn(ESM::Position ESXRecordT::*position, int index, bool door) : Column((door ? Columns::ColumnId_DoorPositionXRot : Columns::ColumnId_PositionXRot) + index, ColumnBase::Display_Double) , mPosition(position) , mIndex(index) + , mIsDoor(door) { } QVariant get(const Record& record) const override { - int column = this->mColumnId; - if (!record.get().mTeleport && column >= Columns::ColumnId_DoorPositionXRot - && column <= Columns::ColumnId::ColumnId_DoorPositionZRot) - return QVariant(QVariant::UserType); + if (!record.get().mTeleport && mIsDoor) + return QVariant(); const ESM::Position& position = record.get().*mPosition; return osg::RadiansToDegrees(position.rot[mIndex]); } From b8a17b16f7bdec3f47187175644e566903858649 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Tue, 26 Mar 2024 05:46:35 -0500 Subject: [PATCH 320/451] Cleanup(CS): Make TeleportColumn take flags as argument --- apps/opencs/model/world/columnimp.hpp | 5 ++--- apps/opencs/model/world/data.cpp | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index b1c24a0c22..d0cbe0fee6 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1136,9 +1136,8 @@ namespace CSMWorld template struct TeleportColumn : public Column { - TeleportColumn() - : Column(Columns::ColumnId_Teleport, ColumnBase::Display_Boolean, - ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh) + TeleportColumn(int flags) + : Column(Columns::ColumnId_Teleport, ColumnBase::Display_Boolean, flags) { } diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 1bff00701a..9ee6db5d4e 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -591,7 +591,8 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mRefs.addColumn(new ChargesColumn); mRefs.addColumn(new EnchantmentChargesColumn); mRefs.addColumn(new StackSizeColumn); - mRefs.addColumn(new TeleportColumn); + mRefs.addColumn(new TeleportColumn( + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh)); mRefs.addColumn(new TeleportCellColumn); mRefs.addColumn(new PosColumn(&CellRef::mDoorDest, 0, true)); mRefs.addColumn(new PosColumn(&CellRef::mDoorDest, 1, true)); From 8cbcb82dd4eca46d7cdd4b82305b0f7c5010b534 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 28 Mar 2024 20:01:50 +0100 Subject: [PATCH 321/451] Prevent iterator invalidation when updating Lua UI and increase const correctness --- components/lua_ui/container.cpp | 10 +++++----- components/lua_ui/container.hpp | 6 +++--- components/lua_ui/element.cpp | 26 ++++++++++--------------- components/lua_ui/flex.cpp | 4 ++-- components/lua_ui/flex.hpp | 24 +++++++++++++++++------ components/lua_ui/text.cpp | 2 +- components/lua_ui/text.hpp | 2 +- components/lua_ui/textedit.cpp | 2 +- components/lua_ui/textedit.hpp | 2 +- components/lua_ui/widget.cpp | 25 +++++++++++++----------- components/lua_ui/widget.hpp | 34 ++++++++++++++++++++++++--------- 11 files changed, 81 insertions(+), 56 deletions(-) diff --git a/components/lua_ui/container.cpp b/components/lua_ui/container.cpp index 52fea684d7..1999be8169 100644 --- a/components/lua_ui/container.cpp +++ b/components/lua_ui/container.cpp @@ -10,12 +10,12 @@ namespace LuaUi updateSizeToFit(); } - MyGUI::IntSize LuaContainer::childScalingSize() + MyGUI::IntSize LuaContainer::childScalingSize() const { return MyGUI::IntSize(); } - MyGUI::IntSize LuaContainer::templateScalingSize() + MyGUI::IntSize LuaContainer::templateScalingSize() const { return mInnerSize; } @@ -23,14 +23,14 @@ namespace LuaUi void LuaContainer::updateSizeToFit() { MyGUI::IntSize innerSize = MyGUI::IntSize(); - for (auto w : children()) + for (const auto w : children()) { MyGUI::IntCoord coord = w->calculateCoord(); innerSize.width = std::max(innerSize.width, coord.left + coord.width); innerSize.height = std::max(innerSize.height, coord.top + coord.height); } MyGUI::IntSize outerSize = innerSize; - for (auto w : templateChildren()) + for (const auto w : templateChildren()) { MyGUI::IntCoord coord = w->calculateCoord(); outerSize.width = std::max(outerSize.width, coord.left + coord.width); @@ -40,7 +40,7 @@ namespace LuaUi mOuterSize = outerSize; } - MyGUI::IntSize LuaContainer::calculateSize() + MyGUI::IntSize LuaContainer::calculateSize() const { return mOuterSize; } diff --git a/components/lua_ui/container.hpp b/components/lua_ui/container.hpp index 16f19d3c12..ef13dd0638 100644 --- a/components/lua_ui/container.hpp +++ b/components/lua_ui/container.hpp @@ -10,13 +10,13 @@ namespace LuaUi MYGUI_RTTI_DERIVED(LuaContainer) public: - MyGUI::IntSize calculateSize() override; + MyGUI::IntSize calculateSize() const override; void updateCoord() override; protected: void updateChildren() override; - MyGUI::IntSize childScalingSize() override; - MyGUI::IntSize templateScalingSize() override; + MyGUI::IntSize childScalingSize() const override; + MyGUI::IntSize templateScalingSize() const override; private: void updateSizeToFit(); diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index ffd763b40b..ceaa746f15 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -54,25 +54,19 @@ namespace LuaUi if (!ext->isRoot()) destroyWidget(ext); else - ext->detachFromParent(); + ext->detachFromParent(false); } void detachElements(WidgetExtension* ext) { - for (auto* child : ext->children()) - { + auto predicate = [](WidgetExtension* child) { if (child->isRoot()) - child->detachFromParent(); - else - detachElements(child); - } - for (auto* child : ext->templateChildren()) - { - if (child->isRoot()) - child->detachFromParent(); - else - detachElements(child); - } + return true; + detachElements(child); + return false; + }; + ext->detachChildrenIf(predicate); + ext->detachTemplateChildrenIf(predicate); } void destroyRoot(WidgetExtension* ext) @@ -194,8 +188,8 @@ namespace LuaUi throw std::logic_error(std::string("Invalid widget type ") += type); std::string name = layout.get_or(LayoutKeys::name, std::string()); - MyGUI::Widget* widget = MyGUI::Gui::getInstancePtr()->createWidgetT( - type, "", MyGUI::IntCoord(), MyGUI::Align::Default, std::string(), name); + MyGUI::Widget* widget + = MyGUI::Gui::getInstancePtr()->createWidgetT(type, {}, {}, MyGUI::Align::Default, {}, name); WidgetExtension* ext = dynamic_cast(widget); if (!ext) diff --git a/components/lua_ui/flex.cpp b/components/lua_ui/flex.cpp index c55b48ddb7..1a3293d406 100644 --- a/components/lua_ui/flex.cpp +++ b/components/lua_ui/flex.cpp @@ -79,7 +79,7 @@ namespace LuaUi WidgetExtension::updateChildren(); } - MyGUI::IntSize LuaFlex::childScalingSize() + MyGUI::IntSize LuaFlex::childScalingSize() const { // Call the base method to prevent relativeSize feedback loop MyGUI::IntSize size = WidgetExtension::calculateSize(); @@ -88,7 +88,7 @@ namespace LuaUi return size; } - MyGUI::IntSize LuaFlex::calculateSize() + MyGUI::IntSize LuaFlex::calculateSize() const { MyGUI::IntSize size = WidgetExtension::calculateSize(); if (mAutoSized) diff --git a/components/lua_ui/flex.hpp b/components/lua_ui/flex.hpp index 944daaec77..c91ffd00a2 100644 --- a/components/lua_ui/flex.hpp +++ b/components/lua_ui/flex.hpp @@ -11,10 +11,10 @@ namespace LuaUi MYGUI_RTTI_DERIVED(LuaFlex) protected: - MyGUI::IntSize calculateSize() override; + MyGUI::IntSize calculateSize() const override; void updateProperties() override; void updateChildren() override; - MyGUI::IntSize childScalingSize() override; + MyGUI::IntSize childScalingSize() const override; void updateCoord() override; @@ -26,25 +26,37 @@ namespace LuaUi Alignment mArrange; template - T& primary(MyGUI::types::TPoint& point) + T& primary(MyGUI::types::TPoint& point) const { return mHorizontal ? point.left : point.top; } template - T& secondary(MyGUI::types::TPoint& point) + T& secondary(MyGUI::types::TPoint& point) const { return mHorizontal ? point.top : point.left; } template - T& primary(MyGUI::types::TSize& size) + T& primary(MyGUI::types::TSize& size) const { return mHorizontal ? size.width : size.height; } template - T& secondary(MyGUI::types::TSize& size) + T& secondary(MyGUI::types::TSize& size) const + { + return mHorizontal ? size.height : size.width; + } + + template + T primary(const MyGUI::types::TSize& size) const + { + return mHorizontal ? size.width : size.height; + } + + template + T secondary(const MyGUI::types::TSize& size) const { return mHorizontal ? size.height : size.width; } diff --git a/components/lua_ui/text.cpp b/components/lua_ui/text.cpp index e55f1750b9..35aa9402bf 100644 --- a/components/lua_ui/text.cpp +++ b/components/lua_ui/text.cpp @@ -46,7 +46,7 @@ namespace LuaUi updateCoord(); } - MyGUI::IntSize LuaText::calculateSize() + MyGUI::IntSize LuaText::calculateSize() const { if (mAutoSized) return getTextSize(); diff --git a/components/lua_ui/text.hpp b/components/lua_ui/text.hpp index fffe34a3ba..87c01a37e8 100644 --- a/components/lua_ui/text.hpp +++ b/components/lua_ui/text.hpp @@ -21,7 +21,7 @@ namespace LuaUi bool mAutoSized; protected: - MyGUI::IntSize calculateSize() override; + MyGUI::IntSize calculateSize() const override; }; } diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index e12bd20c35..9bd241884a 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -63,7 +63,7 @@ namespace LuaUi mEditBox->attachToWidget(this); } - MyGUI::IntSize LuaTextEdit::calculateSize() + MyGUI::IntSize LuaTextEdit::calculateSize() const { MyGUI::IntSize normalSize = WidgetExtension::calculateSize(); if (mAutoSize) diff --git a/components/lua_ui/textedit.hpp b/components/lua_ui/textedit.hpp index 8f23b51746..57e1209aff 100644 --- a/components/lua_ui/textedit.hpp +++ b/components/lua_ui/textedit.hpp @@ -20,7 +20,7 @@ namespace LuaUi void updateProperties() override; void updateCoord() override; void updateChildren() override; - MyGUI::IntSize calculateSize() override; + MyGUI::IntSize calculateSize() const override; private: void textChange(MyGUI::EditBox*); diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index e61c36c452..3804e70096 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -101,7 +101,7 @@ namespace LuaUi void WidgetExtension::attach(WidgetExtension* ext) { if (ext->mParent != this) - ext->detachFromParent(); + ext->detachFromParent(true); ext->mParent = this; ext->mTemplateChild = false; ext->widget()->attachToWidget(mSlot->widget()); @@ -114,13 +114,16 @@ namespace LuaUi ext->widget()->attachToWidget(widget()); } - void WidgetExtension::detachFromParent() + void WidgetExtension::detachFromParent(bool updateParent) { if (mParent) { - auto children = mParent->children(); - std::erase(children, this); - mParent->setChildren(children); + if (updateParent) + { + auto children = mParent->children(); + std::erase(children, this); + mParent->setChildren(children); + } mParent = nullptr; } widget()->detachFromWidget(); @@ -307,7 +310,7 @@ namespace LuaUi w->updateCoord(); } - MyGUI::IntSize WidgetExtension::parentSize() + MyGUI::IntSize WidgetExtension::parentSize() const { if (!mParent) return widget()->getParentSize(); // size of the layer @@ -317,7 +320,7 @@ namespace LuaUi return mParent->childScalingSize(); } - MyGUI::IntSize WidgetExtension::calculateSize() + MyGUI::IntSize WidgetExtension::calculateSize() const { if (mForceSize) return mForcedCoord.size(); @@ -330,7 +333,7 @@ namespace LuaUi return newSize; } - MyGUI::IntPoint WidgetExtension::calculatePosition(const MyGUI::IntSize& size) + MyGUI::IntPoint WidgetExtension::calculatePosition(const MyGUI::IntSize& size) const { if (mForcePosition) return mForcedCoord.point(); @@ -342,7 +345,7 @@ namespace LuaUi return newPosition; } - MyGUI::IntCoord WidgetExtension::calculateCoord() + MyGUI::IntCoord WidgetExtension::calculateCoord() const { MyGUI::IntCoord newCoord; newCoord = calculateSize(); @@ -350,12 +353,12 @@ namespace LuaUi return newCoord; } - MyGUI::IntSize WidgetExtension::childScalingSize() + MyGUI::IntSize WidgetExtension::childScalingSize() const { return mSlot->widget()->getSize(); } - MyGUI::IntSize WidgetExtension::templateScalingSize() + MyGUI::IntSize WidgetExtension::templateScalingSize() const { return widget()->getSize(); } diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 05359705a1..59ac997688 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -31,11 +31,13 @@ namespace LuaUi virtual void deinitialize(); MyGUI::Widget* widget() const { return mWidget; } - WidgetExtension* slot() const { return mSlot; } bool isRoot() const { return mElementRoot; } WidgetExtension* getParent() const { return mParent; } - void detachFromParent(); + void detachFromParent(bool updateParent); + + void detachChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mChildren); } + void detachTemplateChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mTemplateChildren); } void reset(); @@ -65,14 +67,14 @@ namespace LuaUi void setLayout(const sol::table& layout) { mLayout = layout; } template - T externalValue(std::string_view name, const T& defaultValue) + T externalValue(std::string_view name, const T& defaultValue) const { return parseExternal(mExternal, name, defaultValue); } - virtual MyGUI::IntSize calculateSize(); - virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size); - MyGUI::IntCoord calculateCoord(); + virtual MyGUI::IntSize calculateSize() const; + virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size) const; + MyGUI::IntCoord calculateCoord() const; virtual bool isTextInput() { return false; } @@ -85,9 +87,9 @@ namespace LuaUi sol::object keyEvent(MyGUI::KeyCode) const; sol::object mouseEvent(int left, int top, MyGUI::MouseButton button) const; - MyGUI::IntSize parentSize(); - virtual MyGUI::IntSize childScalingSize(); - virtual MyGUI::IntSize templateScalingSize(); + MyGUI::IntSize parentSize() const; + virtual MyGUI::IntSize childScalingSize() const; + virtual MyGUI::IntSize templateScalingSize() const; template T propertyValue(std::string_view name, const T& defaultValue) @@ -176,6 +178,20 @@ namespace LuaUi void focusLoss(MyGUI::Widget*, MyGUI::Widget*); void updateVisible(); + + void detachChildrenIf(auto&& predicate, std::vector children) + { + for (auto it = children.begin(); it != children.end();) + { + if (predicate(*it)) + { + (*it)->detachFromParent(false); + it = children.erase(it); + } + else + ++it; + } + } }; class LuaWidget : public MyGUI::Widget, public WidgetExtension From 1d13f7db8f380635767bb935caf7e5c8fc1a6a50 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 28 Mar 2024 20:17:05 +0100 Subject: [PATCH 322/451] Simplify detachFromParent --- components/lua_ui/element.cpp | 2 +- components/lua_ui/widget.cpp | 23 +++++++++++------------ components/lua_ui/widget.hpp | 4 ++-- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index ceaa746f15..f3f873a583 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -54,7 +54,7 @@ namespace LuaUi if (!ext->isRoot()) destroyWidget(ext); else - ext->detachFromParent(false); + ext->detachFromParent(); } void detachElements(WidgetExtension* ext) diff --git a/components/lua_ui/widget.cpp b/components/lua_ui/widget.cpp index 3804e70096..e7e1053ab7 100644 --- a/components/lua_ui/widget.cpp +++ b/components/lua_ui/widget.cpp @@ -101,7 +101,15 @@ namespace LuaUi void WidgetExtension::attach(WidgetExtension* ext) { if (ext->mParent != this) - ext->detachFromParent(true); + { + if (ext->mParent) + { + auto children = ext->mParent->children(); + std::erase(children, this); + ext->mParent->setChildren(children); + } + ext->detachFromParent(); + } ext->mParent = this; ext->mTemplateChild = false; ext->widget()->attachToWidget(mSlot->widget()); @@ -114,18 +122,9 @@ namespace LuaUi ext->widget()->attachToWidget(widget()); } - void WidgetExtension::detachFromParent(bool updateParent) + void WidgetExtension::detachFromParent() { - if (mParent) - { - if (updateParent) - { - auto children = mParent->children(); - std::erase(children, this); - mParent->setChildren(children); - } - mParent = nullptr; - } + mParent = nullptr; widget()->detachFromWidget(); } diff --git a/components/lua_ui/widget.hpp b/components/lua_ui/widget.hpp index 59ac997688..24962f6820 100644 --- a/components/lua_ui/widget.hpp +++ b/components/lua_ui/widget.hpp @@ -34,7 +34,7 @@ namespace LuaUi bool isRoot() const { return mElementRoot; } WidgetExtension* getParent() const { return mParent; } - void detachFromParent(bool updateParent); + void detachFromParent(); void detachChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mChildren); } void detachTemplateChildrenIf(auto&& predicate) { detachChildrenIf(predicate, mTemplateChildren); } @@ -185,7 +185,7 @@ namespace LuaUi { if (predicate(*it)) { - (*it)->detachFromParent(false); + (*it)->detachFromParent(); it = children.erase(it); } else From 76105cc2d1efe12d736f9f3dec409bd1a8e04d90 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 29 Mar 2024 09:34:52 +0300 Subject: [PATCH 323/451] Make sunlight scattering and wobbly shores optional --- apps/openmw/mwgui/settingswindow.cpp | 17 +++++++++++ apps/openmw/mwgui/settingswindow.hpp | 4 +++ apps/openmw/mwrender/water.cpp | 2 ++ components/settings/categories/water.hpp | 2 ++ .../reference/modding/settings/water.rst | 28 +++++++++++++++++ files/data/l10n/OMWEngine/en.yaml | 2 ++ files/data/l10n/OMWEngine/ru.yaml | 2 ++ .../data/mygui/openmw_settings_window.layout | 30 ++++++++++++++++++- files/settings-default.cfg | 6 ++++ files/shaders/compatibility/water.frag | 17 ++++++++--- 10 files changed, 105 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index b569132141..396d0b18a3 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -266,6 +266,9 @@ namespace MWGui getWidget(mResetControlsButton, "ResetControlsButton"); getWidget(mKeyboardSwitch, "KeyboardButton"); getWidget(mControllerSwitch, "ControllerButton"); + getWidget(mWaterRefractionButton, "WaterRefractionButton"); + getWidget(mSunlightScatteringButton, "SunlightScatteringButton"); + getWidget(mWobblyShoresButton, "WobblyShoresButton"); getWidget(mWaterTextureSize, "WaterTextureSize"); getWidget(mWaterReflectionDetail, "WaterReflectionDetail"); getWidget(mWaterRainRippleDetail, "WaterRainRippleDetail"); @@ -306,6 +309,8 @@ namespace MWGui += MyGUI::newDelegate(this, &SettingsWindow::onTextureFilteringChanged); mResolutionList->eventListChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onResolutionSelected); + mWaterRefractionButton->eventMouseButtonClick + += MyGUI::newDelegate(this, &SettingsWindow::onRefractionButtonClicked); mWaterTextureSize->eventComboChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onWaterTextureSizeChanged); mWaterReflectionDetail->eventComboChangePosition @@ -377,6 +382,10 @@ namespace MWGui const int waterRainRippleDetail = Settings::water().mRainRippleDetail; mWaterRainRippleDetail->setIndexSelected(waterRainRippleDetail); + const bool waterRefraction = Settings::water().mRefraction; + mSunlightScatteringButton->setEnabled(waterRefraction); + mWobblyShoresButton->setEnabled(waterRefraction); + updateMaxLightsComboBox(mMaxLights); const Settings::WindowMode windowMode = Settings::video().mWindowMode; @@ -504,6 +513,14 @@ namespace MWGui } } + void SettingsWindow::onRefractionButtonClicked(MyGUI::Widget* _sender) + { + const bool refractionEnabled = Settings::water().mRefraction; + + mSunlightScatteringButton->setEnabled(refractionEnabled); + mWobblyShoresButton->setEnabled(refractionEnabled); + } + void SettingsWindow::onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos) { int size = 0; diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 1f96f7de54..dc4e09f8ac 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -37,6 +37,9 @@ namespace MWGui MyGUI::Button* mWindowBorderButton; MyGUI::ComboBox* mTextureFilteringButton; + MyGUI::Button* mWaterRefractionButton; + MyGUI::Button* mSunlightScatteringButton; + MyGUI::Button* mWobblyShoresButton; MyGUI::ComboBox* mWaterTextureSize; MyGUI::ComboBox* mWaterReflectionDetail; MyGUI::ComboBox* mWaterRainRippleDetail; @@ -76,6 +79,7 @@ namespace MWGui void onResolutionCancel(); void highlightCurrentResolution(); + void onRefractionButtonClicked(MyGUI::Widget* _sender); void onWaterTextureSizeChanged(MyGUI::ComboBox* _sender, size_t pos); void onWaterReflectionDetailChanged(MyGUI::ComboBox* _sender, size_t pos); void onWaterRainRippleDetailChanged(MyGUI::ComboBox* _sender, size_t pos); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 2afaa06ad0..62266d6e2d 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -705,6 +705,8 @@ namespace MWRender defineMap["rain_ripple_detail"] = std::to_string(rippleDetail); defineMap["ripple_map_world_scale"] = std::to_string(RipplesSurface::sWorldScaleFactor); defineMap["ripple_map_size"] = std::to_string(RipplesSurface::sRTTSize) + ".0"; + defineMap["sunlightScattering"] = Settings::water().mSunlightScattering ? "1" : "0"; + defineMap["wobblyShores"] = Settings::water().mWobblyShores ? "1" : "0"; Stereo::shaderStereoDefines(defineMap); diff --git a/components/settings/categories/water.hpp b/components/settings/categories/water.hpp index 2e04114244..63adce4ee3 100644 --- a/components/settings/categories/water.hpp +++ b/components/settings/categories/water.hpp @@ -26,6 +26,8 @@ namespace Settings SettingValue mSmallFeatureCullingPixelSize{ mIndex, "Water", "small feature culling pixel size", makeMaxStrictSanitizerFloat(0) }; SettingValue mRefractionScale{ mIndex, "Water", "refraction scale", makeClampSanitizerFloat(0, 1) }; + SettingValue mSunlightScattering{ mIndex, "Water", "sunlight scattering" }; + SettingValue mWobblyShores{ mIndex, "Water", "wobbly shores" }; }; } diff --git a/docs/source/reference/modding/settings/water.rst b/docs/source/reference/modding/settings/water.rst index fe407071f2..b04b92de94 100644 --- a/docs/source/reference/modding/settings/water.rst +++ b/docs/source/reference/modding/settings/water.rst @@ -58,6 +58,34 @@ This setting has no effect if the shader setting is false. This setting can be toggled with the 'Refraction' button in the Water tab of the Video panel of the Options menu. +sunlight scattering +------------------- + +:Type: boolean +:Range: True/False +:Default: True + +This setting enables sunlight scattering. +This makes incident sunlight seemingly spread through water, simulating the optical property. + +This setting has no effect if refraction is turned off. + +This setting can be toggled with the 'Sunlight Scattering' button in the Water tab of the Video panel of the Options menu. + +wobbly shores +------------- + +:Type: boolean +:Range: True/False +:Default: True + +This setting makes shores wobbly. +The water surface will smoothly fade into the shoreline and wobble based on water normal-mapping, which avoids harsh transitions. + +This setting has no effect if refraction is turned off. + +This setting can be toggled with the 'Wobbly Shores' button in the Water tab of the Video panel of the Options menu. + reflection detail ----------------- diff --git a/files/data/l10n/OMWEngine/en.yaml b/files/data/l10n/OMWEngine/en.yaml index 55ebbf3e94..d14aaa78fa 100644 --- a/files/data/l10n/OMWEngine/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -154,6 +154,7 @@ SensitivityHigh: "High" SensitivityLow: "Low" SettingsWindow: "Options" Subtitles: "Subtitles" +SunlightScattering: "Sunlight Scattering" TestingExteriorCells: "Testing Exterior Cells" TestingInteriorCells: "Testing Interior Cells" TextureFiltering: "Texture Filtering" @@ -178,3 +179,4 @@ WindowModeFullscreen: "Fullscreen" WindowModeHint: "Hint: Windowed Fullscreen mode\nalways uses the native display resolution." WindowModeWindowed: "Windowed" WindowModeWindowedFullscreen: "Windowed Fullscreen" +WobblyShores: "Wobbly Shores" diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index 07fc376675..1edecbf8b0 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -154,6 +154,7 @@ SensitivityHigh: "Высокая" SensitivityLow: "Низкая" SettingsWindow: "Настройки" Subtitles: "Субтитры" +SunlightScattering: "Рассеяние солнечного света" TestingExteriorCells: "Проверка наружных ячеек" TestingInteriorCells: "Проверка ячеек-помещений" TextureFiltering: "Фильтрация текстур" @@ -178,3 +179,4 @@ WindowModeFullscreen: "Полный экран" WindowModeHint: "Подсказка: режим Оконный без полей\nвсегда использует родное разрешение экрана." WindowModeWindowed: "Оконный" WindowModeWindowedFullscreen: "Оконный без полей" +WobblyShores: "Колеблющиеся берега" diff --git a/files/data/mygui/openmw_settings_window.layout b/files/data/mygui/openmw_settings_window.layout index 9e2f707ef5..5a25a61936 100644 --- a/files/data/mygui/openmw_settings_window.layout +++ b/files/data/mygui/openmw_settings_window.layout @@ -457,7 +457,7 @@ - + @@ -467,6 +467,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 58f6c347f1..73331867a7 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -680,6 +680,12 @@ small feature culling pixel size = 20.0 # By what factor water downscales objects. Only works with water shader and refractions on. refraction scale = 1.0 +# Make incident sunlight spread through water. +sunlight scattering = true + +# Fade and wobble water plane edges to avoid harsh shoreline transitions. +wobbly shores = true + [Windows] # Location and sizes of windows as a fraction of the OpenMW window or diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index c971f92b99..2debf2fac0 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -41,12 +41,13 @@ const float BUMP_RAIN = 2.5; const float REFL_BUMP = 0.10; // reflection distortion amount const float REFR_BUMP = 0.07; // refraction distortion amount +#if @sunlightScattering const float SCATTER_AMOUNT = 0.3; // amount of sunlight scattering const vec3 SCATTER_COLOUR = vec3(0.0,1.0,0.95); // colour of sunlight scattering +const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); // sunlight extinction +#endif -const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction const float SUN_SPEC_FADING_THRESHOLD = 0.15; // visibility at which sun specularity starts to fade - const float SPEC_HARDNESS = 256.0; // specular highlights hardness const float BUMP_SUPPRESS_DEPTH = 300.0; // at what water depth bumpmap will be suppressed for reflections and refractions (prevents artifacts at shores) @@ -57,7 +58,9 @@ const float WIND_SPEED = 0.2f; const vec3 WATER_COLOR = vec3(0.090195, 0.115685, 0.12745); +#if @wobblyShores const float WOBBLY_SHORE_FADE_DISTANCE = 6200.0; // fade out wobbly shores to mask precision errors, the effect is almost impossible to see at a distance +#endif // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - @@ -213,7 +216,7 @@ void main(void) refraction = mix(refraction, waterColor, clamp(factor, 0.0, 1.0)); } - // sunlight scattering +#if @sunlightScattering // normal for sunlight scattering vec3 lNormal = (normal0 * bigWaves.x * 0.5 + normal1 * bigWaves.y * 0.5 + normal2 * midWaves.x * 0.2 + normal3 * midWaves.y * 0.2 + normal4 * smallWaves.x * 0.1 + normal5 * smallWaves.y * 0.1 + rippleAdd); @@ -222,9 +225,13 @@ void main(void) vec3 scatterColour = mix(SCATTER_COLOUR*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0)); vec3 lR = reflect(lVec, lNormal); float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * sunSpec.a * clamp(1.0-exp(-sunHeight), 0.0, 1.0); - gl_FragData[0].xyz = mix(mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; + refraction = mix(refraction, scatterColour, lightScatter); +#endif + + gl_FragData[0].xyz = mix(refraction, reflection, fresnel) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; gl_FragData[0].w = 1.0; +#if @wobblyShores // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping vec3 normalShoreRippleRain = texture2D(normalMap,normalCoords(UV, 2.0, 2.7, -1.0*waterTimer, 0.05, 0.1, normal3)).rgb - 0.5 + texture2D(normalMap,normalCoords(UV, 2.0, 2.7, waterTimer, 0.04, -0.13, normal4)).rgb - 0.5; @@ -234,6 +241,8 @@ void main(void) shoreOffset *= fuzzFactor; shoreOffset = clamp(mix(shoreOffset, 1.0, clamp(linearDepth / WOBBLY_SHORE_FADE_DISTANCE, 0.0, 1.0)), 0.0, 1.0); gl_FragData[0].xyz = mix(rawRefraction, gl_FragData[0].xyz, shoreOffset); +#endif + #else gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.a, 0.0, 1.0); From 3d83585c46290e1959d1b7f88217f6fd3a584db4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Mar 2024 11:46:04 +0400 Subject: [PATCH 324/451] Make binding names layout-independent (bug 7908) --- CHANGELOG.md | 1 + apps/openmw/mwlua/inputbindings.cpp | 2 +- extern/oics/ICSInputControlSystem.cpp | 6 +----- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f797a98dab..fa332b3074 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -165,6 +165,7 @@ Bug #7898: Editor: Invalid reference scales are allowed Bug #7899: Editor: Doors can't be unlocked Bug #7901: Editor: Teleport-related fields shouldn't be editable if a ref does not teleport + Bug #7908: Key bindings names in the settings menu are layout-specific Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index e9ed4fe485..09a5a0babb 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -221,7 +221,7 @@ namespace MWLua api["getControlSwitch"] = [input](std::string_view key) { return input->getControlSwitch(key); }; api["setControlSwitch"] = [input](std::string_view key, bool v) { input->toggleControlSwitch(key, v); }; - api["getKeyName"] = [](SDL_Scancode code) { return SDL_GetKeyName(SDL_GetKeyFromScancode(code)); }; + api["getKeyName"] = [](SDL_Scancode code) { return SDL_GetScancodeName(code); }; api["ACTION"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ { "GameMenu", MWInput::A_GameMenu }, diff --git a/extern/oics/ICSInputControlSystem.cpp b/extern/oics/ICSInputControlSystem.cpp index 9f03ec121e..bc860524ef 100644 --- a/extern/oics/ICSInputControlSystem.cpp +++ b/extern/oics/ICSInputControlSystem.cpp @@ -802,11 +802,7 @@ namespace ICS std::string InputControlSystem::scancodeToString(SDL_Scancode key) { - SDL_Keycode code = SDL_GetKeyFromScancode(key); - if (code == SDLK_UNKNOWN) - return std::string(SDL_GetScancodeName(key)); - else - return std::string(SDL_GetKeyName(code)); + return std::string(SDL_GetScancodeName(key)); } void InputControlSystem::adjustMouseRegion(Uint16 width, Uint16 height) From 387e53b4684a595f64f787867a718c839b659a7d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 29 Mar 2024 12:09:50 +0400 Subject: [PATCH 325/451] Add missing initialization --- extern/oics/ICSInputControlSystem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/extern/oics/ICSInputControlSystem.cpp b/extern/oics/ICSInputControlSystem.cpp index 9f03ec121e..063ff0f355 100644 --- a/extern/oics/ICSInputControlSystem.cpp +++ b/extern/oics/ICSInputControlSystem.cpp @@ -42,6 +42,7 @@ namespace ICS , mXmouseAxisBinded(false), mYmouseAxisBinded(false) , mClientWidth(1) , mClientHeight(1) + , mMouseAxisBindingInitialValues{0} { ICS_LOG(" - Creating InputControlSystem - "); From 9a24e77d3f6c65e617e939aff177314f968d485e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 17 Mar 2024 19:01:11 +0100 Subject: [PATCH 326/451] Show F4 stats in pages --- apps/openmw/engine.cpp | 12 +- components/resource/stats.cpp | 629 +++++++++++++++++----------------- components/resource/stats.hpp | 45 +-- 3 files changed, 339 insertions(+), 347 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 49833040d6..63473fe67d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -965,17 +965,17 @@ void OMW::Engine::go() } // Setup profiler - osg::ref_ptr statshandler = new Resource::Profiler(stats.is_open(), mVFS.get()); + osg::ref_ptr statsHandler = new Resource::Profiler(stats.is_open(), *mVFS); - initStatsHandler(*statshandler); + initStatsHandler(*statsHandler); - mViewer->addEventHandler(statshandler); + mViewer->addEventHandler(statsHandler); - osg::ref_ptr resourceshandler = new Resource::StatsHandler(stats.is_open(), mVFS.get()); - mViewer->addEventHandler(resourceshandler); + osg::ref_ptr resourcesHandler = new Resource::StatsHandler(stats.is_open(), *mVFS); + mViewer->addEventHandler(resourcesHandler); if (stats.is_open()) - Resource::CollectStatistics(mViewer); + Resource::collectStatistics(*mViewer); // Start the game if (!mSaveGameFile.empty()) diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 0542ffef28..5d88a55e57 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -2,7 +2,11 @@ #include #include +#include #include +#include +#include +#include #include @@ -18,143 +22,207 @@ namespace Resource { - - static bool collectStatRendering = false; - static bool collectStatCameraObjects = false; - static bool collectStatViewerObjects = false; - static bool collectStatResource = false; - static bool collectStatGPU = false; - static bool collectStatEvent = false; - static bool collectStatFrameRate = false; - static bool collectStatUpdate = false; - static bool collectStatEngine = false; - - static const VFS::Path::Normalized sFontName("Fonts/DejaVuLGCSansMono.ttf"); - - static void setupStatCollection() + namespace { - const char* envList = getenv("OPENMW_OSG_STATS_LIST"); - if (envList == nullptr) - return; + constexpr float statsWidth = 1280.0f; + constexpr float statsHeight = 1024.0f; + constexpr float characterSize = 17.0f; + constexpr float backgroundMargin = 5; + constexpr float backgroundSpacing = 3; + constexpr float maxStatsHeight = 420.0f; + constexpr std::size_t pageSize + = static_cast((maxStatsHeight - 2 * backgroundMargin) / characterSize); + constexpr int statsHandlerKey = osgGA::GUIEventAdapter::KEY_F4; + const VFS::Path::Normalized fontName("Fonts/DejaVuLGCSansMono.ttf"); + + bool collectStatRendering = false; + bool collectStatCameraObjects = false; + bool collectStatViewerObjects = false; + bool collectStatResource = false; + bool collectStatGPU = false; + bool collectStatEvent = false; + bool collectStatFrameRate = false; + bool collectStatUpdate = false; + bool collectStatEngine = false; + + const std::vector allStatNames = { + "FrameNumber", + "Compiling", + "WorkQueue", + "WorkThread", + "UnrefQueue", + "Texture", + "StateSet", + "Node", + "Shape", + "Shape Instance", + "Image", + "Nif", + "Keyframe", + "Groundcover Chunk", + "Object Chunk", + "Terrain Chunk", + "Terrain Texture", + "Land", + "Composite", + "Mechanics Actors", + "Mechanics Objects", + "Physics Actors", + "Physics Objects", + "Physics Projectiles", + "Physics HeightFields", + "Lua UsedMemory", + "NavMesh Jobs", + "NavMesh Waiting", + "NavMesh Pushed", + "NavMesh Processing", + "NavMesh DbJobs Write", + "NavMesh DbJobs Read", + "NavMesh DbCache Get", + "NavMesh DbCache Hit", + "NavMesh CacheSize", + "NavMesh UsedTiles", + "NavMesh CachedTiles", + "NavMesh Cache Get", + "NavMesh Cache Hit", + }; + + void setupStatCollection() + { + const char* envList = getenv("OPENMW_OSG_STATS_LIST"); + if (envList == nullptr) + return; - std::string_view kwList(envList); + std::string_view kwList(envList); - auto kwBegin = kwList.begin(); + auto kwBegin = kwList.begin(); - while (kwBegin != kwList.end()) - { - auto kwEnd = std::find(kwBegin, kwList.end(), ';'); - - const auto kw = kwList.substr(std::distance(kwList.begin(), kwBegin), std::distance(kwBegin, kwEnd)); - - if (kw == "gpu") - collectStatGPU = true; - else if (kw == "event") - collectStatEvent = true; - else if (kw == "frame_rate") - collectStatFrameRate = true; - else if (kw == "update") - collectStatUpdate = true; - else if (kw == "engine") - collectStatEngine = true; - else if (kw == "rendering") - collectStatRendering = true; - else if (kw == "cameraobjects") - collectStatCameraObjects = true; - else if (kw == "viewerobjects") - collectStatViewerObjects = true; - else if (kw == "resource") - collectStatResource = true; - else if (kw == "times") + while (kwBegin != kwList.end()) { - collectStatGPU = true; - collectStatEvent = true; - collectStatFrameRate = true; - collectStatUpdate = true; - collectStatEngine = true; - collectStatRendering = true; - } + auto kwEnd = std::find(kwBegin, kwList.end(), ';'); + + const auto kw = kwList.substr(std::distance(kwList.begin(), kwBegin), std::distance(kwBegin, kwEnd)); + + if (kw == "gpu") + collectStatGPU = true; + else if (kw == "event") + collectStatEvent = true; + else if (kw == "frame_rate") + collectStatFrameRate = true; + else if (kw == "update") + collectStatUpdate = true; + else if (kw == "engine") + collectStatEngine = true; + else if (kw == "rendering") + collectStatRendering = true; + else if (kw == "cameraobjects") + collectStatCameraObjects = true; + else if (kw == "viewerobjects") + collectStatViewerObjects = true; + else if (kw == "resource") + collectStatResource = true; + else if (kw == "times") + { + collectStatGPU = true; + collectStatEvent = true; + collectStatFrameRate = true; + collectStatUpdate = true; + collectStatEngine = true; + collectStatRendering = true; + } - if (kwEnd == kwList.end()) - break; + if (kwEnd == kwList.end()) + break; - kwBegin = std::next(kwEnd); + kwBegin = std::next(kwEnd); + } } - } - class SetFontVisitor : public osg::NodeVisitor - { - public: - SetFontVisitor(osgText::Font* font) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mFont(font) + osg::ref_ptr createBackgroundRectangle( + const osg::Vec3& pos, const float width, const float height, const osg::Vec4& color) { + osg::ref_ptr geometry = new osg::Geometry; + + geometry->setUseDisplayList(false); + + osg::ref_ptr stateSet = new osg::StateSet; + geometry->setStateSet(stateSet); + + osg::ref_ptr vertices = new osg::Vec3Array; + vertices->push_back(osg::Vec3(pos.x(), pos.y(), 0)); + vertices->push_back(osg::Vec3(pos.x(), pos.y() - height, 0)); + vertices->push_back(osg::Vec3(pos.x() + width, pos.y() - height, 0)); + vertices->push_back(osg::Vec3(pos.x() + width, pos.y(), 0)); + geometry->setVertexArray(vertices); + + osg::ref_ptr colors = new osg::Vec4Array; + colors->push_back(color); + geometry->setColorArray(colors, osg::Array::BIND_OVERALL); + + osg::ref_ptr base + = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_FAN, 0); + base->push_back(0); + base->push_back(1); + base->push_back(2); + base->push_back(3); + geometry->addPrimitiveSet(base); + + return geometry; } - void apply(osg::Drawable& node) override + osg::ref_ptr getMonoFont(const VFS::Manager& vfs) { - if (osgText::Text* text = dynamic_cast(&node)) + if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf") && vfs.exists(fontName)) { - text->setFont(mFont); + const Files::IStreamPtr streamPtr = vfs.get(fontName); + return osgText::readRefFontStream(*streamPtr); } - } - private: - osgText::Font* mFont; - }; - - osg::ref_ptr getMonoFont(VFS::Manager* vfs) - { - if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf") && vfs->exists(sFontName)) - { - Files::IStreamPtr streamPtr = vfs->get(sFontName); - return osgText::readRefFontStream(*streamPtr.get()); + return nullptr; } - return nullptr; - } - - StatsHandler::StatsHandler(bool offlineCollect, VFS::Manager* vfs) - : _key(osgGA::GUIEventAdapter::KEY_F4) - , _initialized(false) - , _statsType(false) - , _offlineCollect(offlineCollect) - , _statsWidth(1280.0f) - , _statsHeight(1024.0f) - , _characterSize(18.0f) - { - _camera = new osg::Camera; - _camera->getOrCreateStateSet()->setGlobalDefaults(); - _camera->setRenderer(new osgViewer::Renderer(_camera.get())); - _camera->setProjectionResizePolicy(osg::Camera::FIXED); + class SetFontVisitor : public osg::NodeVisitor + { + public: + SetFontVisitor(osgText::Font* font) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mFont(font) + { + } - _resourceStatsChildNum = 0; + void apply(osg::Drawable& node) override + { + if (osgText::Text* text = dynamic_cast(&node)) + { + text->setFont(mFont); + } + } - _textFont = getMonoFont(vfs); + private: + osgText::Font* mFont; + }; } - Profiler::Profiler(bool offlineCollect, VFS::Manager* vfs) - : _offlineCollect(offlineCollect) - , _initFonts(false) + Profiler::Profiler(bool offlineCollect, const VFS::Manager& vfs) + : mOfflineCollect(offlineCollect) + , mTextFont(getMonoFont(vfs)) { - _characterSize = 18; + _characterSize = characterSize; _font.clear(); - _textFont = getMonoFont(vfs); - setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3); setupStatCollection(); } void Profiler::setUpFonts() { - if (_textFont != nullptr) + if (mTextFont != nullptr) { - SetFontVisitor visitor(_textFont); + SetFontVisitor visitor(mTextFont); _switch->accept(visitor); } - _initFonts = true; + mInitFonts = true; } bool Profiler::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) @@ -162,24 +230,44 @@ namespace Resource osgViewer::ViewerBase* viewer = nullptr; bool handled = StatsHandler::handle(ea, aa); - if (_initialized && !_initFonts) + if (_initialized && !mInitFonts) setUpFonts(); auto* view = dynamic_cast(&aa); if (view) viewer = view->getViewerBase(); - if (viewer) + if (viewer != nullptr) { // Add/remove openmw stats to the osd as necessary viewer->getViewerStats()->collectStats("engine", _statsType >= StatsHandler::StatsType::VIEWER_STATS); - if (_offlineCollect) - CollectStatistics(viewer); + if (mOfflineCollect) + collectStatistics(*viewer); } return handled; } + StatsHandler::StatsHandler(bool offlineCollect, const VFS::Manager& vfs) + : mOfflineCollect(offlineCollect) + , mSwitch(new osg::Switch) + , mCamera(new osg::Camera) + , mTextFont(getMonoFont(vfs)) + { + osg::ref_ptr stateset = mSwitch->getOrCreateStateSet(); + stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + stateset->setMode(GL_BLEND, osg::StateAttribute::ON); + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); +#ifdef OSG_GL1_AVAILABLE + stateset->setAttribute(new osg::PolygonMode(), osg::StateAttribute::PROTECTED); +#endif + + mCamera->getOrCreateStateSet()->setGlobalDefaults(); + mCamera->setRenderer(new osgViewer::Renderer(mCamera.get())); + mCamera->setProjectionResizePolicy(osg::Camera::FIXED); + mCamera->addChild(mSwitch); + } + bool StatsHandler::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) { if (ea.getHandled()) @@ -189,18 +277,21 @@ namespace Resource { case (osgGA::GUIEventAdapter::KEYDOWN): { - if (ea.getKey() == _key) + if (ea.getKey() == statsHandlerKey) { - osgViewer::View* myview = dynamic_cast(&aa); - if (!myview) + osgViewer::View* const view = dynamic_cast(&aa); + if (view == nullptr) return false; - osgViewer::ViewerBase* viewer = myview->getViewerBase(); + osgViewer::ViewerBase* const viewer = view->getViewerBase(); - toggle(viewer); + if (viewer == nullptr) + return false; - if (_offlineCollect) - CollectStatistics(viewer); + toggle(*viewer); + + if (mOfflineCollect) + collectStatistics(*viewer); aa.requestRedraw(); return true; @@ -223,66 +314,69 @@ namespace Resource if (width <= 0 || height <= 0) return; - _camera->setViewport(0, 0, width, height); - if (fabs(height * _statsWidth) <= fabs(width * _statsHeight)) + mCamera->setViewport(0, 0, width, height); + if (std::abs(height * statsWidth) <= std::abs(width * statsHeight)) { - _camera->setProjectionMatrix( - osg::Matrix::ortho2D(_statsWidth - width * _statsHeight / height, _statsWidth, 0.0, _statsHeight)); + mCamera->setProjectionMatrix( + osg::Matrix::ortho2D(statsWidth - width * statsHeight / height, statsWidth, 0.0, statsHeight)); } else { - _camera->setProjectionMatrix( - osg::Matrix::ortho2D(0.0, _statsWidth, _statsHeight - height * _statsWidth / width, _statsHeight)); + mCamera->setProjectionMatrix( + osg::Matrix::ortho2D(0.0, statsWidth, statsHeight - height * statsWidth / width, statsHeight)); } } - void StatsHandler::toggle(osgViewer::ViewerBase* viewer) + void StatsHandler::toggle(osgViewer::ViewerBase& viewer) { - if (!_initialized) + if (!mInitialized) { setUpHUDCamera(viewer); setUpScene(viewer); + mInitialized = true; } - _statsType = !_statsType; - - if (!_statsType) + if (mPage == mSwitch->getNumChildren()) { - _camera->setNodeMask(0); - _switch->setAllChildrenOff(); + mPage = 0; + + mCamera->setNodeMask(0); + mSwitch->setAllChildrenOff(); - viewer->getViewerStats()->collectStats("resource", false); + viewer.getViewerStats()->collectStats("resource", false); } else { - _camera->setNodeMask(0xffffffff); - _switch->setSingleChildOn(_resourceStatsChildNum); + mCamera->setNodeMask(0xffffffff); + mSwitch->setSingleChildOn(mPage); - viewer->getViewerStats()->collectStats("resource", true); + viewer.getViewerStats()->collectStats("resource", true); + + ++mPage; } } - void StatsHandler::setUpHUDCamera(osgViewer::ViewerBase* viewer) + void StatsHandler::setUpHUDCamera(osgViewer::ViewerBase& viewer) { // Try GraphicsWindow first so we're likely to get the main viewer window - osg::GraphicsContext* context = dynamic_cast(_camera->getGraphicsContext()); + osg::GraphicsContext* context = dynamic_cast(mCamera->getGraphicsContext()); if (!context) { osgViewer::Viewer::Windows windows; - viewer->getWindows(windows); + viewer.getWindows(windows); if (!windows.empty()) context = windows.front(); else { // No GraphicsWindows were found, so let's try to find a GraphicsContext - context = _camera->getGraphicsContext(); + context = mCamera->getGraphicsContext(); if (!context) { osgViewer::Viewer::Contexts contexts; - viewer->getContexts(contexts); + viewer.getContexts(contexts); if (contexts.empty()) return; @@ -292,241 +386,151 @@ namespace Resource } } - _camera->setGraphicsContext(context); + mCamera->setGraphicsContext(context); - _camera->setRenderOrder(osg::Camera::POST_RENDER, 11); + mCamera->setRenderOrder(osg::Camera::POST_RENDER, 11); - _camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); - _camera->setViewMatrix(osg::Matrix::identity()); + mCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + mCamera->setViewMatrix(osg::Matrix::identity()); setWindowSize(context->getTraits()->width, context->getTraits()->height); // only clear the depth buffer - _camera->setClearMask(0); - _camera->setAllowEventFocus(false); - - _camera->setRenderer(new osgViewer::Renderer(_camera.get())); + mCamera->setClearMask(0); + mCamera->setAllowEventFocus(false); - _initialized = true; + mCamera->setRenderer(new osgViewer::Renderer(mCamera.get())); } - osg::Geometry* createBackgroundRectangle( - const osg::Vec3& pos, const float width, const float height, osg::Vec4& color) + namespace { - osg::StateSet* ss = new osg::StateSet; - - osg::Geometry* geometry = new osg::Geometry; - - geometry->setUseDisplayList(false); - geometry->setStateSet(ss); - - osg::Vec3Array* vertices = new osg::Vec3Array; - geometry->setVertexArray(vertices); - - vertices->push_back(osg::Vec3(pos.x(), pos.y(), 0)); - vertices->push_back(osg::Vec3(pos.x(), pos.y() - height, 0)); - vertices->push_back(osg::Vec3(pos.x() + width, pos.y() - height, 0)); - vertices->push_back(osg::Vec3(pos.x() + width, pos.y(), 0)); - - osg::Vec4Array* colors = new osg::Vec4Array; - colors->push_back(color); - geometry->setColorArray(colors, osg::Array::BIND_OVERALL); - - osg::DrawElementsUShort* base = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_FAN, 0); - base->push_back(0); - base->push_back(1); - base->push_back(2); - base->push_back(3); - - geometry->addPrimitiveSet(base); - - return geometry; - } - - class ResourceStatsTextDrawCallback : public osg::Drawable::DrawCallback - { - public: - ResourceStatsTextDrawCallback(osg::Stats* stats, const std::vector& statNames) - : mStats(stats) - , mStatNames(statNames) + class ResourceStatsTextDrawCallback : public osg::Drawable::DrawCallback { - } + public: + explicit ResourceStatsTextDrawCallback(osg::Stats* stats, std::span statNames) + : mStats(stats) + , mStatNames(statNames) + { + } - void drawImplementation(osg::RenderInfo& renderInfo, const osg::Drawable* drawable) const override - { - if (!mStats) - return; + void drawImplementation(osg::RenderInfo& renderInfo, const osg::Drawable* drawable) const override + { + if (mStats == nullptr) + return; - osgText::Text* text = (osgText::Text*)(drawable); + osgText::Text* text = (osgText::Text*)(drawable); - std::ostringstream viewStr; - viewStr.setf(std::ios::left, std::ios::adjustfield); - viewStr.width(14); - // Used fixed formatting, as scientific will switch to "...e+.." notation for - // large numbers of vertices/drawables/etc. - viewStr.setf(std::ios::fixed); - viewStr.precision(0); + std::ostringstream viewStr; + viewStr.setf(std::ios::left, std::ios::adjustfield); + viewStr.width(14); + // Used fixed formatting, as scientific will switch to "...e+.." notation for + // large numbers of vertices/drawables/etc. + viewStr.setf(std::ios::fixed); + viewStr.precision(0); - unsigned int frameNumber = renderInfo.getState()->getFrameStamp()->getFrameNumber() - 1; + const unsigned int frameNumber = renderInfo.getState()->getFrameStamp()->getFrameNumber() - 1; - for (const auto& statName : mStatNames.get()) - { - if (statName.empty()) - viewStr << std::endl; - else + for (const std::string& statName : mStatNames) { - double value = 0.0; - if (mStats->getAttribute(frameNumber, statName, value)) - viewStr << std::setw(8) << value << std::endl; + if (statName.empty()) + viewStr << std::endl; else - viewStr << std::setw(8) << "." << std::endl; + { + double value = 0.0; + if (mStats->getAttribute(frameNumber, statName, value)) + viewStr << std::setw(8) << value << std::endl; + else + viewStr << std::setw(8) << "." << std::endl; + } } - } - text->setText(viewStr.str()); + text->setText(viewStr.str()); - text->drawImplementation(renderInfo); - } + text->drawImplementation(renderInfo); + } - osg::ref_ptr mStats; - std::reference_wrapper> mStatNames; - }; + private: + osg::ref_ptr mStats; + std::span mStatNames; + }; + } - void StatsHandler::setUpScene(osgViewer::ViewerBase* viewer) + void StatsHandler::setUpScene(osgViewer::ViewerBase& viewer) { - _switch = new osg::Switch; - - _camera->addChild(_switch); - - osg::StateSet* stateset = _switch->getOrCreateStateSet(); - stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); - stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); -#ifdef OSG_GL1_AVAILABLE - stateset->setAttribute(new osg::PolygonMode(), osg::StateAttribute::PROTECTED); -#endif - - osg::Vec4 backgroundColor(0.0, 0.0, 0.0f, 0.3); - osg::Vec4 staticTextColor(1.0, 1.0, 0.0f, 1.0); - osg::Vec4 dynamicTextColor(1.0, 1.0, 1.0f, 1.0); - float backgroundMargin = 5; - float backgroundSpacing = 3; - - // resource stats + const osg::Vec4 backgroundColor(0.0, 0.0, 0.0f, 0.3); + const osg::Vec4 staticTextColor(1.0, 1.0, 0.0f, 1.0); + const osg::Vec4 dynamicTextColor(1.0, 1.0, 1.0f, 1.0); + + const auto longest = std::max_element(allStatNames.begin(), allStatNames.end(), + [](const std::string& lhs, const std::string& rhs) { return lhs.size() < rhs.size(); }); + const std::size_t longestSize = longest->size(); + const float statNamesWidth = longestSize * characterSize * 0.6 + 2 * backgroundMargin; + const float statTextWidth = 7 * characterSize + 2 * backgroundMargin; + const float statHeight = pageSize * characterSize + 2 * backgroundMargin; + const float width = statNamesWidth + backgroundSpacing + statTextWidth; + + for (std::size_t offset = 0; offset < allStatNames.size(); offset += pageSize) { - osg::Group* group = new osg::Group; + osg::ref_ptr group = new osg::Group; + group->setCullingActive(false); - _resourceStatsChildNum = _switch->getNumChildren(); - _switch->addChild(group, false); - - static const std::vector statNames({ - "FrameNumber", - "", - "Compiling", - "WorkQueue", - "WorkThread", - "UnrefQueue", - "", - "Texture", - "StateSet", - "Node", - "Shape", - "Shape Instance", - "Image", - "Nif", - "Keyframe", - "", - "Groundcover Chunk", - "Object Chunk", - "Terrain Chunk", - "Terrain Texture", - "Land", - "Composite", - "", - "NavMesh Jobs", - "NavMesh Waiting", - "NavMesh Pushed", - "NavMesh Processing", - "NavMesh DbJobs Write", - "NavMesh DbJobs Read", - "NavMesh DbCache Get", - "NavMesh DbCache Hit", - "NavMesh CacheSize", - "NavMesh UsedTiles", - "NavMesh CachedTiles", - "NavMesh Cache Get", - "NavMesh Cache Hit", - "", - "Mechanics Actors", - "Mechanics Objects", - "", - "Physics Actors", - "Physics Objects", - "Physics Projectiles", - "Physics HeightFields", - "", - "Lua UsedMemory", - }); - - static const auto longest = std::max_element(statNames.begin(), statNames.end(), - [](const std::string& lhs, const std::string& rhs) { return lhs.size() < rhs.size(); }); - const float statNamesWidth = 13 * _characterSize + 2 * backgroundMargin; - const float statTextWidth = 7 * _characterSize + 2 * backgroundMargin; - const float statHeight = statNames.size() * _characterSize + 2 * backgroundMargin; - osg::Vec3 pos(_statsWidth - statNamesWidth - backgroundSpacing - statTextWidth, statHeight, 0.0f); + + const std::size_t count = std::min(allStatNames.size() - offset, pageSize); + std::span currentStatNames(allStatNames.data() + offset, count); + osg::Vec3 pos(statsWidth - width, statHeight - characterSize, 0.0f); group->addChild( - createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, _characterSize + backgroundMargin, 0), + createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, backgroundMargin + characterSize, 0), statNamesWidth, statHeight, backgroundColor)); osg::ref_ptr staticText = new osgText::Text; group->addChild(staticText.get()); staticText->setColor(staticTextColor); - staticText->setCharacterSize(_characterSize); + staticText->setCharacterSize(characterSize); staticText->setPosition(pos); std::ostringstream viewStr; viewStr.clear(); viewStr.setf(std::ios::left, std::ios::adjustfield); - viewStr.width(longest->size()); - for (const auto& statName : statNames) - { + viewStr.width(longestSize); + for (const std::string& statName : currentStatNames) viewStr << statName << std::endl; - } staticText->setText(viewStr.str()); pos.x() += statNamesWidth + backgroundSpacing; group->addChild( - createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, _characterSize + backgroundMargin, 0), + createBackgroundRectangle(pos + osg::Vec3(-backgroundMargin, backgroundMargin + characterSize, 0), statTextWidth, statHeight, backgroundColor)); osg::ref_ptr statsText = new osgText::Text; group->addChild(statsText.get()); statsText->setColor(dynamicTextColor); - statsText->setCharacterSize(_characterSize); + statsText->setCharacterSize(characterSize); statsText->setPosition(pos); statsText->setText(""); - statsText->setDrawCallback(new ResourceStatsTextDrawCallback(viewer->getViewerStats(), statNames)); + statsText->setDrawCallback(new ResourceStatsTextDrawCallback(viewer.getViewerStats(), currentStatNames)); - if (_textFont) + if (mTextFont != nullptr) { - staticText->setFont(_textFont); - statsText->setFont(_textFont); + staticText->setFont(mTextFont); + statsText->setFont(mTextFont); } + + mSwitch->addChild(group, false); } } void StatsHandler::getUsage(osg::ApplicationUsage& usage) const { - usage.addKeyboardMouseBinding(_key, "On screen resource usage stats."); + usage.addKeyboardMouseBinding(statsHandlerKey, "On screen resource usage stats."); } - void CollectStatistics(osgViewer::ViewerBase* viewer) + void collectStatistics(osgViewer::ViewerBase& viewer) { osgViewer::Viewer::Cameras cameras; - viewer->getCameras(cameras); + viewer.getCameras(cameras); for (auto* camera : cameras) { if (collectStatGPU) @@ -537,17 +541,16 @@ namespace Resource camera->getStats()->collectStats("scene", true); } if (collectStatEvent) - viewer->getViewerStats()->collectStats("event", true); + viewer.getViewerStats()->collectStats("event", true); if (collectStatFrameRate) - viewer->getViewerStats()->collectStats("frame_rate", true); + viewer.getViewerStats()->collectStats("frame_rate", true); if (collectStatUpdate) - viewer->getViewerStats()->collectStats("update", true); + viewer.getViewerStats()->collectStats("update", true); if (collectStatResource) - viewer->getViewerStats()->collectStats("resource", true); + viewer.getViewerStats()->collectStats("resource", true); if (collectStatViewerObjects) - viewer->getViewerStats()->collectStats("scene", true); + viewer.getViewerStats()->collectStats("scene", true); if (collectStatEngine) - viewer->getViewerStats()->collectStats("engine", true); + viewer.getViewerStats()->collectStats("engine", true); } - } diff --git a/components/resource/stats.hpp b/components/resource/stats.hpp index fc2899d386..7381dac417 100644 --- a/components/resource/stats.hpp +++ b/components/resource/stats.hpp @@ -28,57 +28,46 @@ namespace Resource class Profiler : public osgViewer::StatsHandler { public: - Profiler(bool offlineCollect, VFS::Manager* vfs); + explicit Profiler(bool offlineCollect, const VFS::Manager& vfs); + bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override; private: void setUpFonts(); - bool _offlineCollect; - bool _initFonts; - osg::ref_ptr _textFont; + bool mInitFonts = false; + bool mOfflineCollect; + osg::ref_ptr mTextFont; }; class StatsHandler : public osgGA::GUIEventHandler { public: - StatsHandler(bool offlineCollect, VFS::Manager* vfs); - - void setKey(int key) { _key = key; } - int getKey() const { return _key; } + explicit StatsHandler(bool offlineCollect, const VFS::Manager& vfs); bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) override; - void setWindowSize(int w, int h); - - void toggle(osgViewer::ViewerBase* viewer); - - void setUpHUDCamera(osgViewer::ViewerBase* viewer); - void setUpScene(osgViewer::ViewerBase* viewer); - /** Get the keyboard and mouse usage of this manipulator.*/ void getUsage(osg::ApplicationUsage& usage) const override; private: - osg::ref_ptr _switch; - int _key; - osg::ref_ptr _camera; - bool _initialized; - bool _statsType; - bool _offlineCollect; + unsigned mPage = 0; + bool mInitialized = false; + bool mOfflineCollect; + osg::ref_ptr mSwitch; + osg::ref_ptr mCamera; + osg::ref_ptr mTextFont; - float _statsWidth; - float _statsHeight; + void setWindowSize(int w, int h); - float _characterSize; + void toggle(osgViewer::ViewerBase& viewer); - int _resourceStatsChildNum; + void setUpHUDCamera(osgViewer::ViewerBase& viewer); - osg::ref_ptr _textFont; + void setUpScene(osgViewer::ViewerBase& viewer); }; - void CollectStatistics(osgViewer::ViewerBase* viewer); - + void collectStatistics(osgViewer::ViewerBase& viewer); } #endif From ae41ebfc836c34d61ae6fe72d389e7ec57077d4e Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 21 Dec 2023 22:38:45 +0100 Subject: [PATCH 327/451] Report CellPreloader stats --- apps/openmw/mwworld/cellpreloader.cpp | 16 +++++++++++++++- apps/openmw/mwworld/cellpreloader.hpp | 15 +++++++++++++++ apps/openmw/mwworld/scene.cpp | 5 +++++ apps/openmw/mwworld/scene.hpp | 3 +++ apps/openmw/mwworld/worldimp.cpp | 1 + components/resource/stats.cpp | 5 +++++ 6 files changed, 44 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index 364f3e169e..7157e67d82 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include #include #include @@ -276,6 +278,7 @@ namespace MWWorld { oldestCell->second.mWorkItem->abort(); mPreloadCells.erase(oldestCell); + ++mEvicted; } else return; @@ -285,7 +288,8 @@ namespace MWWorld mResourceSystem->getKeyframeManager(), mTerrain, mLandManager, mPreloadInstances)); mWorkQueue->addWorkItem(item); - mPreloadCells[&cell] = PreloadEntry(timestamp, item); + mPreloadCells.emplace(&cell, PreloadEntry(timestamp, item)); + ++mAdded; } void CellPreloader::notifyLoaded(CellStore* cell) @@ -300,6 +304,7 @@ namespace MWWorld } mPreloadCells.erase(found); + ++mLoaded; } } @@ -329,6 +334,7 @@ namespace MWWorld it->second.mWorkItem = nullptr; } mPreloadCells.erase(it++); + ++mExpired; } else ++it; @@ -467,4 +473,12 @@ namespace MWWorld mPreloadCells.clear(); } + void CellPreloader::reportStats(unsigned int frameNumber, osg::Stats& stats) const + { + stats.setAttribute(frameNumber, "CellPreloader Count", mPreloadCells.size()); + stats.setAttribute(frameNumber, "CellPreloader Added", mAdded); + stats.setAttribute(frameNumber, "CellPreloader Evicted", mEvicted); + stats.setAttribute(frameNumber, "CellPreloader Loaded", mLoaded); + stats.setAttribute(frameNumber, "CellPreloader Expired", mExpired); + } } diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index ddf13cab83..ce5d5e7a0f 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -2,11 +2,20 @@ #define OPENMW_MWWORLD_CELLPRELOADER_H #include + #include + #include #include #include +#include + +namespace osg +{ + class Stats; +} + namespace Resource { class ResourceSystem; @@ -76,6 +85,8 @@ namespace MWWorld bool isTerrainLoaded(const CellPreloader::PositionCellGrid& position, double referenceTime) const; void setTerrain(Terrain::World* terrain); + void reportStats(unsigned int frameNumber, osg::Stats& stats) const; + private: void clearAllTasks(); @@ -118,6 +129,10 @@ namespace MWWorld std::vector mLoadedTerrainPositions; double mLoadedTerrainTimestamp; + std::size_t mEvicted = 0; + std::size_t mAdded = 0; + std::size_t mExpired = 0; + std::size_t mLoaded = 0; }; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index a5787e301e..64a258cff8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1285,4 +1285,9 @@ namespace MWWorld } } } + + void Scene::reportStats(unsigned int frameNumber, osg::Stats& stats) const + { + mPreloader->reportStats(frameNumber, stats); + } } diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index fdca9bb87f..6c915d4f92 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -19,6 +19,7 @@ namespace osg { class Vec3f; + class Stats; } namespace ESM @@ -203,6 +204,8 @@ namespace MWWorld void testExteriorCells(); void testInteriorCells(); + + void reportStats(unsigned int frameNumber, osg::Stats& stats) const; }; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 4fc7a21339..ec58f779d0 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3786,6 +3786,7 @@ namespace MWWorld { DetourNavigator::reportStats(mNavigator->getStats(), frameNumber, stats); mPhysics->reportStats(frameNumber, stats); + mWorldScene->reportStats(frameNumber, stats); } void World::updateSkyDate() diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 5d88a55e57..e361be36dd 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -72,6 +72,11 @@ namespace Resource "Physics Projectiles", "Physics HeightFields", "Lua UsedMemory", + "CellPreloader Count", + "CellPreloader Added", + "CellPreloader Evicted", + "CellPreloader Loaded", + "CellPreloader Expired", "NavMesh Jobs", "NavMesh Waiting", "NavMesh Pushed", From 215404e126d20f1aa067be1a212a95387cd64601 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 22 Dec 2023 00:23:49 +0100 Subject: [PATCH 328/451] Report more stats from caches --- apps/openmw/mwrender/groundcover.cpp | 2 +- apps/openmw/mwrender/landmanager.cpp | 4 +- apps/openmw/mwrender/objectpaging.cpp | 2 +- .../resource/testobjectcache.cpp | 32 +++- components/CMakeLists.txt | 2 +- components/resource/bulletshapemanager.cpp | 4 +- components/resource/cachestats.cpp | 40 +++++ components/resource/cachestats.hpp | 28 ++++ components/resource/imagemanager.cpp | 2 +- components/resource/keyframemanager.cpp | 3 +- components/resource/multiobjectcache.cpp | 14 +- components/resource/multiobjectcache.hpp | 7 +- components/resource/niffilemanager.cpp | 3 +- components/resource/objectcache.hpp | 78 +++++----- components/resource/scenemanager.cpp | 2 +- components/resource/stats.cpp | 146 ++++++++++++------ components/resource/stats.hpp | 1 + components/terrain/chunkmanager.cpp | 2 +- components/terrain/texturemanager.cpp | 3 +- 19 files changed, 267 insertions(+), 108 deletions(-) create mode 100644 components/resource/cachestats.cpp create mode 100644 components/resource/cachestats.hpp diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 4f99ee7560..7a29f1bb07 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -463,6 +463,6 @@ namespace MWRender void Groundcover::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Groundcover Chunk", mCache->getCacheSize()); + Resource::reportStats("Groundcover Chunk", frameNumber, mCache->getStats(), *stats); } } diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp index f8a3ebd962..d17933b2b7 100644 --- a/apps/openmw/mwrender/landmanager.cpp +++ b/apps/openmw/mwrender/landmanager.cpp @@ -1,7 +1,5 @@ #include "landmanager.hpp" -#include - #include #include #include @@ -53,7 +51,7 @@ namespace MWRender void LandManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Land", mCache->getCacheSize()); + Resource::reportStats("Land", frameNumber, mCache->getStats(), *stats); } } diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index f1bccc13c9..f426672784 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -1013,7 +1013,7 @@ namespace MWRender void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Object Chunk", mCache->getCacheSize()); + Resource::reportStats("Object Chunk", frameNumber, mCache->getStats(), *stats); } } diff --git a/apps/openmw_test_suite/resource/testobjectcache.cpp b/apps/openmw_test_suite/resource/testobjectcache.cpp index 1c8cff6af0..e2f5799edb 100644 --- a/apps/openmw_test_suite/resource/testobjectcache.cpp +++ b/apps/openmw_test_suite/resource/testobjectcache.cpp @@ -114,9 +114,11 @@ namespace Resource cache->update(referenceTime, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + ASSERT_EQ(cache->getStats().mExpired, 0); cache->update(referenceTime + expiryDelay, expiryDelay); EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + ASSERT_EQ(cache->getStats().mExpired, 1); } TEST(ResourceGenericObjectCacheTest, updateShouldKeepExternallyReferencedItems) @@ -249,7 +251,7 @@ namespace Resource EXPECT_THAT(actual, ElementsAre(Pair(1, value1.get()), Pair(2, value2.get()), Pair(3, value3.get()))); } - TEST(ResourceGenericObjectCacheTest, getCacheSizeShouldReturnNumberOrAddedItems) + TEST(ResourceGenericObjectCacheTest, getStatsShouldReturnNumberOrAddedItems) { osg::ref_ptr> cache(new GenericObjectCache); @@ -258,7 +260,33 @@ namespace Resource cache->addEntryToObjectCache(13, value1); cache->addEntryToObjectCache(42, value2); - EXPECT_EQ(cache->getCacheSize(), 2); + const CacheStats stats = cache->getStats(); + + EXPECT_EQ(stats.mSize, 2); + } + + TEST(ResourceGenericObjectCacheTest, getStatsShouldReturnNumberOrGetsAndHits) + { + osg::ref_ptr> cache(new GenericObjectCache); + + { + const CacheStats stats = cache->getStats(); + + EXPECT_EQ(stats.mGet, 0); + EXPECT_EQ(stats.mHit, 0); + } + + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(13, value); + cache->getRefFromObjectCache(13); + cache->getRefFromObjectCache(42); + + { + const CacheStats stats = cache->getStats(); + + EXPECT_EQ(stats.mGet, 2); + EXPECT_EQ(stats.mHit, 1); + } } TEST(ResourceGenericObjectCacheTest, lowerBoundShouldReturnFirstNotLessThatGivenKey) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index ef01e19460..7ac01ef169 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -125,7 +125,7 @@ add_component_dir (vfs add_component_dir (resource scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem - resourcemanager stats animation foreachbulletobject errormarker + resourcemanager stats animation foreachbulletobject errormarker cachestats ) add_component_dir (shader diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index b37e81111d..93c53d8cb0 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -213,8 +213,8 @@ namespace Resource void BulletShapeManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Shape", mCache->getCacheSize()); - stats->setAttribute(frameNumber, "Shape Instance", mInstanceCache->getCacheSize()); + Resource::reportStats("Shape", frameNumber, mCache->getStats(), *stats); + Resource::reportStats("Shape Instance", frameNumber, mInstanceCache->getStats(), *stats); } } diff --git a/components/resource/cachestats.cpp b/components/resource/cachestats.cpp new file mode 100644 index 0000000000..9cc0cea517 --- /dev/null +++ b/components/resource/cachestats.cpp @@ -0,0 +1,40 @@ +#include "cachestats.hpp" + +#include + +namespace Resource +{ + namespace + { + std::string makeAttribute(std::string_view prefix, std::string_view suffix) + { + std::string result; + result.reserve(prefix.size() + 1 + suffix.size()); + result += prefix; + result += ' '; + result += suffix; + return result; + } + } + + void addCacheStatsAttibutes(std::string_view prefix, std::vector& out) + { + constexpr std::string_view suffixes[] = { + "Count", + "Get", + "Hit", + "Expired", + }; + + for (std::string_view suffix : suffixes) + out.push_back(makeAttribute(prefix, suffix)); + } + + void reportStats(std::string_view prefix, unsigned frameNumber, const CacheStats& src, osg::Stats& dst) + { + dst.setAttribute(frameNumber, makeAttribute(prefix, "Count"), static_cast(src.mSize)); + dst.setAttribute(frameNumber, makeAttribute(prefix, "Get"), static_cast(src.mGet)); + dst.setAttribute(frameNumber, makeAttribute(prefix, "Hit"), static_cast(src.mHit)); + dst.setAttribute(frameNumber, makeAttribute(prefix, "Expired"), static_cast(src.mExpired)); + } +} diff --git a/components/resource/cachestats.hpp b/components/resource/cachestats.hpp new file mode 100644 index 0000000000..c25f801dba --- /dev/null +++ b/components/resource/cachestats.hpp @@ -0,0 +1,28 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_CACHESATS +#define OPENMW_COMPONENTS_RESOURCE_CACHESATS + +#include +#include +#include + +namespace osg +{ + class Stats; +} + +namespace Resource +{ + struct CacheStats + { + std::size_t mSize = 0; + std::size_t mGet = 0; + std::size_t mHit = 0; + std::size_t mExpired = 0; + }; + + void addCacheStatsAttibutes(std::string_view prefix, std::vector& out); + + void reportStats(std::string_view prefix, unsigned frameNumber, const CacheStats& src, osg::Stats& dst); +} + +#endif diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 09c7048059..a7d2ef61a1 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -202,7 +202,7 @@ namespace Resource void ImageManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Image", mCache->getCacheSize()); + Resource::reportStats("Image", frameNumber, mCache->getStats(), *stats); } } diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 68b7adbe9a..0cbbe40d60 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -4,7 +4,6 @@ #include -#include #include #include #include @@ -250,7 +249,7 @@ namespace Resource void KeyframeManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Keyframe", mCache->getCacheSize()); + Resource::reportStats("Keyframe", frameNumber, mCache->getStats(), *stats); } } diff --git a/components/resource/multiobjectcache.cpp b/components/resource/multiobjectcache.cpp index f94dabc77c..71500b0ceb 100644 --- a/components/resource/multiobjectcache.cpp +++ b/components/resource/multiobjectcache.cpp @@ -25,6 +25,7 @@ namespace Resource { objectsToRemove.push_back(oitr->second); _objectCache.erase(oitr++); + ++mExpired; } else { @@ -57,13 +58,15 @@ namespace Resource osg::ref_ptr MultiObjectCache::takeFromObjectCache(const std::string& fileName) { std::lock_guard lock(_objectCacheMutex); + ++mGet; ObjectCacheMap::iterator found = _objectCache.find(fileName); if (found == _objectCache.end()) return osg::ref_ptr(); else { - osg::ref_ptr object = found->second; + osg::ref_ptr object = std::move(found->second); _objectCache.erase(found); + ++mHit; return object; } } @@ -79,10 +82,15 @@ namespace Resource } } - unsigned int MultiObjectCache::getCacheSize() const + CacheStats MultiObjectCache::getStats() const { std::lock_guard lock(_objectCacheMutex); - return _objectCache.size(); + return CacheStats{ + .mSize = _objectCache.size(), + .mGet = mGet, + .mHit = mHit, + .mExpired = mExpired, + }; } } diff --git a/components/resource/multiobjectcache.hpp b/components/resource/multiobjectcache.hpp index e1629f3197..654a88b524 100644 --- a/components/resource/multiobjectcache.hpp +++ b/components/resource/multiobjectcache.hpp @@ -8,6 +8,8 @@ #include #include +#include "cachestats.hpp" + namespace osg { class Object; @@ -37,13 +39,16 @@ namespace Resource /** call releaseGLObjects on all objects attached to the object cache.*/ void releaseGLObjects(osg::State* state); - unsigned int getCacheSize() const; + CacheStats getStats() const; protected: typedef std::multimap> ObjectCacheMap; ObjectCacheMap _objectCache; mutable std::mutex _objectCacheMutex; + std::size_t mGet = 0; + std::size_t mHit = 0; + std::size_t mExpired = 0; }; } diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index c66c7de849..481126f304 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -3,7 +3,6 @@ #include #include -#include #include @@ -59,7 +58,7 @@ namespace Resource void NifFileManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Nif", mCache->getCacheSize()); + Resource::reportStats("Nif", frameNumber, mCache->getStats(), *stats); } } diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index dffa0e9fdb..e619b7102c 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -20,6 +20,8 @@ #ifndef OPENMW_COMPONENTS_RESOURCE_OBJECTCACHE #define OPENMW_COMPONENTS_RESOURCE_OBJECTCACHE +#include "cachestats.hpp" + #include #include #include @@ -29,26 +31,28 @@ #include #include #include +#include namespace osg { class Object; class State; class NodeVisitor; + class Stats; } namespace Resource { + struct GenericObjectCacheItem + { + osg::ref_ptr mValue; + double mLastUsage; + }; template class GenericObjectCache : public osg::Referenced { public: - GenericObjectCache() - : osg::Referenced(true) - { - } - // Update last usage timestamp using referenceTime for each cache time if they are not nullptr and referenced // from somewhere else. Remove items with last usage > expiryTime. Note: last usage might be updated from other // places so nullptr or not references elsewhere items are not always removed. @@ -64,6 +68,7 @@ namespace Resource item.mLastUsage = referenceTime; if (item.mLastUsage > expiryTime) return false; + ++mExpired; if (item.mValue != nullptr) objectsToRemove.push_back(std::move(item.mValue)); return true; @@ -105,34 +110,29 @@ namespace Resource osg::ref_ptr getRefFromObjectCache(const auto& key) { std::lock_guard lock(mMutex); - const auto itr = mItems.find(key); - if (itr != mItems.end()) - return itr->second.mValue; - else - return nullptr; + if (Item* const item = find(key)) + return item->mValue; + return nullptr; } std::optional> getRefFromObjectCacheOrNone(const auto& key) { const std::lock_guard lock(mMutex); - const auto it = mItems.find(key); - if (it == mItems.end()) - return std::nullopt; - return it->second.mValue; + if (Item* const item = find(key)) + return item->mValue; + return std::nullopt; } /** Check if an object is in the cache, and if it is, update its usage time stamp. */ bool checkInObjectCache(const auto& key, double timeStamp) { std::lock_guard lock(mMutex); - const auto itr = mItems.find(key); - if (itr != mItems.end()) + if (Item* const item = find(key)) { - itr->second.mLastUsage = timeStamp; + item->mLastUsage = timeStamp; return true; } - else - return false; + return false; } /** call releaseGLObjects on all objects attached to the object cache.*/ @@ -162,13 +162,6 @@ namespace Resource f(k, v.mValue.get()); } - /** Get the number of objects in the cache. */ - unsigned int getCacheSize() const - { - std::lock_guard lock(mMutex); - return mItems.size(); - } - template std::optional>> lowerBound(K&& key) { @@ -179,21 +172,36 @@ namespace Resource return std::pair(it->first, it->second.mValue); } - protected: - struct Item + CacheStats getStats() const { - osg::ref_ptr mValue; - double mLastUsage; - }; + const std::lock_guard lock(mMutex); + return CacheStats{ + .mSize = mItems.size(), + .mGet = mGet, + .mHit = mHit, + .mExpired = mExpired, + }; + } + + protected: + using Item = GenericObjectCacheItem; std::map> mItems; mutable std::mutex mMutex; - }; + std::size_t mGet = 0; + std::size_t mHit = 0; + std::size_t mExpired = 0; - class ObjectCache : public GenericObjectCache - { + Item* find(const auto& key) + { + ++mGet; + const auto it = mItems.find(key); + if (it == mItems.end()) + return nullptr; + ++mHit; + return &it->second; + } }; - } #endif diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 45c84f093f..e4d0b4363d 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -1125,7 +1125,7 @@ namespace Resource stats->setAttribute(frameNumber, "StateSet", mSharedStateManager->getNumSharedStateSets()); } - stats->setAttribute(frameNumber, "Node", mCache->getCacheSize()); + Resource::reportStats("Node", frameNumber, mCache->getStats(), *stats); } osg::ref_ptr SceneManager::createShaderVisitor(const std::string& shaderPrefix) diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index e361be36dd..65b009deff 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -20,6 +20,8 @@ #include +#include "cachestats.hpp" + namespace Resource { namespace @@ -45,52 +47,95 @@ namespace Resource bool collectStatUpdate = false; bool collectStatEngine = false; - const std::vector allStatNames = { - "FrameNumber", - "Compiling", - "WorkQueue", - "WorkThread", - "UnrefQueue", - "Texture", - "StateSet", - "Node", - "Shape", - "Shape Instance", - "Image", - "Nif", - "Keyframe", - "Groundcover Chunk", - "Object Chunk", - "Terrain Chunk", - "Terrain Texture", - "Land", - "Composite", - "Mechanics Actors", - "Mechanics Objects", - "Physics Actors", - "Physics Objects", - "Physics Projectiles", - "Physics HeightFields", - "Lua UsedMemory", - "CellPreloader Count", - "CellPreloader Added", - "CellPreloader Evicted", - "CellPreloader Loaded", - "CellPreloader Expired", - "NavMesh Jobs", - "NavMesh Waiting", - "NavMesh Pushed", - "NavMesh Processing", - "NavMesh DbJobs Write", - "NavMesh DbJobs Read", - "NavMesh DbCache Get", - "NavMesh DbCache Hit", - "NavMesh CacheSize", - "NavMesh UsedTiles", - "NavMesh CachedTiles", - "NavMesh Cache Get", - "NavMesh Cache Hit", - }; + std::vector generateAllStatNames() + { + constexpr std::string_view firstPage[] = { + "FrameNumber", + "", + "Compiling", + "WorkQueue", + "WorkThread", + "UnrefQueue", + "", + "Texture", + "StateSet", + "Composite", + "", + "Mechanics Actors", + "Mechanics Objects", + "", + "Physics Actors", + "Physics Objects", + "Physics Projectiles", + "Physics HeightFields", + "", + "Lua UsedMemory", + "", + "", + "", + "", + }; + + constexpr std::string_view caches[] = { + "Node", + "Shape", + "Shape Instance", + "Image", + "Nif", + "Keyframe", + "Groundcover Chunk", + "Object Chunk", + "Terrain Chunk", + "Terrain Texture", + "Land", + }; + + constexpr std::string_view cellPreloader[] = { + "CellPreloader Count", + "CellPreloader Added", + "CellPreloader Evicted", + "CellPreloader Loaded", + "CellPreloader Expired", + }; + + constexpr std::string_view navMesh[] = { + "NavMesh Jobs", + "NavMesh Waiting", + "NavMesh Pushed", + "NavMesh Processing", + "NavMesh DbJobs Write", + "NavMesh DbJobs Read", + "NavMesh DbCache Get", + "NavMesh DbCache Hit", + "NavMesh CacheSize", + "NavMesh UsedTiles", + "NavMesh CachedTiles", + "NavMesh Cache Get", + "NavMesh Cache Hit", + }; + + std::vector statNames; + + for (std::string_view name : firstPage) + statNames.emplace_back(name); + + for (std::size_t i = 0; i < std::size(caches); ++i) + { + Resource::addCacheStatsAttibutes(caches[i], statNames); + if ((i + 1) % 5 != 0) + statNames.emplace_back(); + } + + for (std::string_view name : cellPreloader) + statNames.emplace_back(name); + + statNames.emplace_back(); + + for (std::string_view name : navMesh) + statNames.emplace_back(name); + + return statNames; + } void setupStatCollection() { @@ -258,6 +303,7 @@ namespace Resource , mSwitch(new osg::Switch) , mCamera(new osg::Camera) , mTextFont(getMonoFont(vfs)) + , mStatNames(generateAllStatNames()) { osg::ref_ptr stateset = mSwitch->getOrCreateStateSet(); stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF); @@ -465,7 +511,7 @@ namespace Resource const osg::Vec4 staticTextColor(1.0, 1.0, 0.0f, 1.0); const osg::Vec4 dynamicTextColor(1.0, 1.0, 1.0f, 1.0); - const auto longest = std::max_element(allStatNames.begin(), allStatNames.end(), + const auto longest = std::max_element(mStatNames.begin(), mStatNames.end(), [](const std::string& lhs, const std::string& rhs) { return lhs.size() < rhs.size(); }); const std::size_t longestSize = longest->size(); const float statNamesWidth = longestSize * characterSize * 0.6 + 2 * backgroundMargin; @@ -473,14 +519,14 @@ namespace Resource const float statHeight = pageSize * characterSize + 2 * backgroundMargin; const float width = statNamesWidth + backgroundSpacing + statTextWidth; - for (std::size_t offset = 0; offset < allStatNames.size(); offset += pageSize) + for (std::size_t offset = 0; offset < mStatNames.size(); offset += pageSize) { osg::ref_ptr group = new osg::Group; group->setCullingActive(false); - const std::size_t count = std::min(allStatNames.size() - offset, pageSize); - std::span currentStatNames(allStatNames.data() + offset, count); + const std::size_t count = std::min(mStatNames.size() - offset, pageSize); + std::span currentStatNames(mStatNames.data() + offset, count); osg::Vec3 pos(statsWidth - width, statHeight - characterSize, 0.0f); group->addChild( diff --git a/components/resource/stats.hpp b/components/resource/stats.hpp index 7381dac417..0ea421e83d 100644 --- a/components/resource/stats.hpp +++ b/components/resource/stats.hpp @@ -57,6 +57,7 @@ namespace Resource osg::ref_ptr mSwitch; osg::ref_ptr mCamera; osg::ref_ptr mTextFont; + std::vector mStatNames; void setWindowSize(int w, int h); diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 8df5dc3a77..7ccd89ac21 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -63,7 +63,7 @@ namespace Terrain void ChunkManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Terrain Chunk", mCache->getCacheSize()); + Resource::reportStats("Terrain Chunk", frameNumber, mCache->getStats(), *stats); } void ChunkManager::clearCache() diff --git a/components/terrain/texturemanager.cpp b/components/terrain/texturemanager.cpp index 360d87bf48..6b388faf69 100644 --- a/components/terrain/texturemanager.cpp +++ b/components/terrain/texturemanager.cpp @@ -1,6 +1,5 @@ #include "texturemanager.hpp" -#include #include #include @@ -56,7 +55,7 @@ namespace Terrain void TextureManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { - stats->setAttribute(frameNumber, "Terrain Texture", mCache->getCacheSize()); + Resource::reportStats("Terrain Texture", frameNumber, mCache->getStats(), *stats); } } From f70bf42a9ec206b13cfca82d208e4b3b0ac24b92 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 29 Mar 2024 21:50:45 +0100 Subject: [PATCH 329/451] Remove superfluous Trading class --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwgui/tradewindow.cpp | 72 +++++++++++++++++++++++- apps/openmw/mwgui/tradewindow.hpp | 3 - apps/openmw/mwmechanics/trading.cpp | 87 ----------------------------- apps/openmw/mwmechanics/trading.hpp | 20 ------- 5 files changed, 72 insertions(+), 112 deletions(-) delete mode 100644 apps/openmw/mwmechanics/trading.cpp delete mode 100644 apps/openmw/mwmechanics/trading.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index f92e8a0bc1..c9bfa87648 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -103,7 +103,7 @@ add_openmw_dir (mwmechanics drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction summoning - character actors objects aistate trading weaponpriority spellpriority weapontype spellutil + character actors objects aistate weaponpriority spellpriority weapontype spellutil spelleffects ) diff --git a/apps/openmw/mwgui/tradewindow.cpp b/apps/openmw/mwgui/tradewindow.cpp index c62d360412..ba752303d2 100644 --- a/apps/openmw/mwgui/tradewindow.cpp +++ b/apps/openmw/mwgui/tradewindow.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -42,6 +43,75 @@ namespace return static_cast(price * count); } + bool haggle(const MWWorld::Ptr& player, const MWWorld::Ptr& merchant, int playerOffer, int merchantOffer) + { + // accept if merchant offer is better than player offer + if (playerOffer <= merchantOffer) + { + return true; + } + + // reject if npc is a creature + if (merchant.getType() != ESM::NPC::sRecordId) + { + return false; + } + + const MWWorld::Store& gmst + = MWBase::Environment::get().getESMStore()->get(); + + // Is the player buying? + bool buying = (merchantOffer < 0); + int a = std::abs(merchantOffer); + int b = std::abs(playerOffer); + int d = (buying) ? int(100 * (a - b) / a) : int(100 * (b - a) / b); + + int clampedDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(merchant); + + const MWMechanics::CreatureStats& merchantStats = merchant.getClass().getCreatureStats(merchant); + const MWMechanics::CreatureStats& playerStats = player.getClass().getCreatureStats(player); + + float a1 = static_cast(player.getClass().getSkill(player, ESM::Skill::Mercantile)); + float b1 = 0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(); + float c1 = 0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(); + float d1 = static_cast(merchant.getClass().getSkill(merchant, ESM::Skill::Mercantile)); + float e1 = 0.1f * merchantStats.getAttribute(ESM::Attribute::Luck).getModified(); + float f1 = 0.2f * merchantStats.getAttribute(ESM::Attribute::Personality).getModified(); + + float dispositionTerm = gmst.find("fDispositionMod")->mValue.getFloat() * (clampedDisposition - 50); + float pcTerm = (dispositionTerm + a1 + b1 + c1) * playerStats.getFatigueTerm(); + float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm(); + float x = gmst.find("fBargainOfferMulti")->mValue.getFloat() * d + + gmst.find("fBargainOfferBase")->mValue.getFloat() + int(pcTerm - npcTerm); + + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + int roll = Misc::Rng::rollDice(100, prng) + 1; + + // reject if roll fails + // (or if player tries to buy things and get money) + if (roll > x || (merchantOffer < 0 && 0 < playerOffer)) + { + return false; + } + + // apply skill gain on successful barter + float skillGain = 0.f; + int finalPrice = std::abs(playerOffer); + int initialMerchantOffer = std::abs(merchantOffer); + + if (!buying && (finalPrice > initialMerchantOffer)) + { + skillGain = std::floor(100.f * (finalPrice - initialMerchantOffer) / finalPrice); + } + else if (buying && (finalPrice < initialMerchantOffer)) + { + skillGain = std::floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); + } + player.getClass().skillUsageSucceeded( + player, ESM::Skill::Mercantile, ESM::Skill::Mercantile_Success, skillGain); + + return true; + } } namespace MWGui @@ -328,7 +398,7 @@ namespace MWGui } } - bool offerAccepted = mTrading.haggle(player, mPtr, mCurrentBalance, mCurrentMerchantOffer); + bool offerAccepted = haggle(player, mPtr, mCurrentBalance, mCurrentMerchantOffer); // apply disposition change if merchant is NPC if (mPtr.getClass().isNpc()) diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index 7d5fd399df..33c39cb269 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -1,8 +1,6 @@ #ifndef MWGUI_TRADEWINDOW_H #define MWGUI_TRADEWINDOW_H -#include "../mwmechanics/trading.hpp" - #include "referenceinterface.hpp" #include "windowbase.hpp" @@ -53,7 +51,6 @@ namespace MWGui ItemView* mItemView; SortFilterItemModel* mSortModel; TradeItemModel* mTradeModel; - MWMechanics::Trading mTrading; static const float sBalanceChangeInitialPause; // in seconds static const float sBalanceChangeInterval; // in seconds diff --git a/apps/openmw/mwmechanics/trading.cpp b/apps/openmw/mwmechanics/trading.cpp deleted file mode 100644 index b7e361c0b9..0000000000 --- a/apps/openmw/mwmechanics/trading.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "trading.hpp" - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/mechanicsmanager.hpp" -#include "../mwbase/world.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" - -#include "creaturestats.hpp" - -namespace MWMechanics -{ - Trading::Trading() {} - - bool Trading::haggle(const MWWorld::Ptr& player, const MWWorld::Ptr& merchant, int playerOffer, int merchantOffer) - { - // accept if merchant offer is better than player offer - if (playerOffer <= merchantOffer) - { - return true; - } - - // reject if npc is a creature - if (merchant.getType() != ESM::NPC::sRecordId) - { - return false; - } - - const MWWorld::Store& gmst - = MWBase::Environment::get().getESMStore()->get(); - - // Is the player buying? - bool buying = (merchantOffer < 0); - int a = std::abs(merchantOffer); - int b = std::abs(playerOffer); - int d = (buying) ? int(100 * (a - b) / a) : int(100 * (b - a) / b); - - int clampedDisposition = MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(merchant); - - const MWMechanics::CreatureStats& merchantStats = merchant.getClass().getCreatureStats(merchant); - const MWMechanics::CreatureStats& playerStats = player.getClass().getCreatureStats(player); - - float a1 = static_cast(player.getClass().getSkill(player, ESM::Skill::Mercantile)); - float b1 = 0.1f * playerStats.getAttribute(ESM::Attribute::Luck).getModified(); - float c1 = 0.2f * playerStats.getAttribute(ESM::Attribute::Personality).getModified(); - float d1 = static_cast(merchant.getClass().getSkill(merchant, ESM::Skill::Mercantile)); - float e1 = 0.1f * merchantStats.getAttribute(ESM::Attribute::Luck).getModified(); - float f1 = 0.2f * merchantStats.getAttribute(ESM::Attribute::Personality).getModified(); - - float dispositionTerm = gmst.find("fDispositionMod")->mValue.getFloat() * (clampedDisposition - 50); - float pcTerm = (dispositionTerm + a1 + b1 + c1) * playerStats.getFatigueTerm(); - float npcTerm = (d1 + e1 + f1) * merchantStats.getFatigueTerm(); - float x = gmst.find("fBargainOfferMulti")->mValue.getFloat() * d - + gmst.find("fBargainOfferBase")->mValue.getFloat() + int(pcTerm - npcTerm); - - auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - int roll = Misc::Rng::rollDice(100, prng) + 1; - - // reject if roll fails - // (or if player tries to buy things and get money) - if (roll > x || (merchantOffer < 0 && 0 < playerOffer)) - { - return false; - } - - // apply skill gain on successful barter - float skillGain = 0.f; - int finalPrice = std::abs(playerOffer); - int initialMerchantOffer = std::abs(merchantOffer); - - if (!buying && (finalPrice > initialMerchantOffer)) - { - skillGain = std::floor(100.f * (finalPrice - initialMerchantOffer) / finalPrice); - } - else if (buying && (finalPrice < initialMerchantOffer)) - { - skillGain = std::floor(100.f * (initialMerchantOffer - finalPrice) / initialMerchantOffer); - } - player.getClass().skillUsageSucceeded( - player, ESM::Skill::Mercantile, ESM::Skill::Mercantile_Success, skillGain); - - return true; - } -} diff --git a/apps/openmw/mwmechanics/trading.hpp b/apps/openmw/mwmechanics/trading.hpp deleted file mode 100644 index e30b82f5e8..0000000000 --- a/apps/openmw/mwmechanics/trading.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef OPENMW_MECHANICS_TRADING_H -#define OPENMW_MECHANICS_TRADING_H - -namespace MWWorld -{ - class Ptr; -} - -namespace MWMechanics -{ - class Trading - { - public: - Trading(); - - bool haggle(const MWWorld::Ptr& player, const MWWorld::Ptr& merchant, int playerOffer, int merchantOffer); - }; -} - -#endif From d08e47bc40b436e3c5114a1eb912cdf581fdcec9 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 29 Mar 2024 22:34:53 +0100 Subject: [PATCH 330/451] Expose AI stats to Lua --- CHANGELOG.md | 1 + apps/openmw/mwlua/stats.cpp | 73 +++++++++++++++++++++++++++++++++- files/lua_api/openmw/types.lua | 34 ++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f797a98dab..4211f47538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -217,6 +217,7 @@ Feature #7792: Support Timescale Clouds Feature #7795: Support MaxNumberRipples INI setting Feature #7805: Lua Menu context + Feature #7860: Lua: Expose NPC AI settings (fight, alarm, flee) Task #5896: Do not use deprecated MyGUI properties Task #6085: Replace boost::filesystem with std::filesystem Task #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index ad0f585207..209a852697 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -31,7 +31,7 @@ namespace using Index = const SelfObject::CachedStat::Index&; template - auto addIndexedAccessor(Index index) + auto addIndexedAccessor(auto index) { return [index](const sol::object& o) { return T::create(ObjectVariant(o), index); }; } @@ -425,6 +425,62 @@ namespace MWLua stats.setSkill(id, stat); } }; + + class AIStat + { + ObjectVariant mObject; + MWMechanics::AiSetting mIndex; + + AIStat(ObjectVariant object, MWMechanics::AiSetting index) + : mObject(std::move(object)) + , mIndex(index) + { + } + + public: + template + sol::object get(const Context& context, std::string_view prop, G getter) const + { + return getValue(context, mObject, &AIStat::setValue, static_cast(mIndex), prop, + [this, getter](const MWWorld::Ptr& ptr) { + return (ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex).*getter)(); + }); + } + + int getModified(const Context& context) const + { + auto base = LuaUtil::cast(get(context, "base", &MWMechanics::Stat::getBase)); + auto modifier = LuaUtil::cast(get(context, "modifier", &MWMechanics::Stat::getModifier)); + return std::max(0, base + modifier); + } + + static std::optional create(ObjectVariant object, MWMechanics::AiSetting index) + { + if (!object.ptr().getClass().isActor()) + return {}; + return AIStat{ std::move(object), index }; + } + + void cache(const Context& context, std::string_view prop, const sol::object& value) const + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &AIStat::setValue, static_cast(mIndex), prop }] = value; + } + + static void setValue(Index i, std::string_view prop, const MWWorld::Ptr& ptr, const sol::object& value) + { + auto index = static_cast(std::get(i)); + auto& stats = ptr.getClass().getCreatureStats(ptr); + auto stat = stats.getAiSetting(index); + int intValue = LuaUtil::cast(value); + if (prop == "base") + stat.setBase(intValue); + else if (prop == "modifier") + stat.setModifier(intValue); + stats.setAiSetting(index, stat); + } + }; } namespace sol @@ -465,6 +521,10 @@ namespace sol struct is_automagical : std::false_type { }; + template <> + struct is_automagical : std::false_type + { + }; } namespace MWLua @@ -529,6 +589,17 @@ namespace MWLua stats["attributes"] = LuaUtil::makeReadOnly(attributes); for (const ESM::Attribute& attribute : MWBase::Environment::get().getESMStore()->get()) attributes[ESM::RefId(attribute.mId).serializeText()] = addIndexedAccessor(attribute.mId); + + auto aiStatT = context.mLua->sol().new_usertype("AIStat"); + addProp(context, aiStatT, "base", &MWMechanics::Stat::getBase); + addProp(context, aiStatT, "modifier", &MWMechanics::Stat::getModifier); + aiStatT["modified"] = sol::readonly_property([=](const AIStat& stat) { return stat.getModified(context); }); + sol::table ai(context.mLua->sol(), sol::create); + stats["ai"] = LuaUtil::makeReadOnly(ai); + ai["alarm"] = addIndexedAccessor(MWMechanics::AiSetting::Alarm); + ai["fight"] = addIndexedAccessor(MWMechanics::AiSetting::Fight); + ai["flee"] = addIndexedAccessor(MWMechanics::AiSetting::Flee); + ai["hello"] = addIndexedAccessor(MWMechanics::AiSetting::Hello); } void addNpcStatsBindings(sol::table& npc, const Context& context) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index e935fcbba3..149d9bd9fa 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -445,6 +445,12 @@ -- @field #number modifier The skill's modifier. -- @field #number progress [0-1] The NPC's skill progress. +--- +-- @type AIStat +-- @field #number base The stat's base value. +-- @field #number modifier The stat's modifier. +-- @field #number modified The actor's current ai value (read-only.) + --- -- @type DynamicStats @@ -466,6 +472,33 @@ -- @param openmw.core#GameObject actor -- @return #DynamicStat +--- +-- @type AIStats + +--- +-- Alarm (returns @{#AIStat}) +-- @function [parent=#AIStats] alarm +-- @param openmw.core#GameObject actor +-- @return #AIStat + +--- +-- Fight (returns @{#AIStat}) +-- @function [parent=#AIStats] fight +-- @param openmw.core#GameObject actor +-- @return #AIStat + +--- +-- Flee (returns @{#AIStat}) +-- @function [parent=#AIStats] flee +-- @param openmw.core#GameObject actor +-- @return #AIStat + +--- +-- Hello (returns @{#AIStat}) +-- @function [parent=#AIStats] hello +-- @param openmw.core#GameObject actor +-- @return #AIStat + --- -- @type AttributeStats @@ -686,6 +719,7 @@ -- @type ActorStats -- @field #DynamicStats dynamic -- @field #AttributeStats attributes +-- @field #AIStats ai --- -- Level (returns @{#LevelStat}) From cb357997c9c07be7e696a62c046133b2ec6d2394 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 30 Mar 2024 14:36:45 +0100 Subject: [PATCH 331/451] Copy DIAL type to INFO when saving --- CHANGELOG.md | 1 + apps/opencs/model/doc/savingstages.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f797a98dab..a8f7ee4d3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -160,6 +160,7 @@ Bug #7840: First run of the launcher doesn't save viewing distance as the default value Bug #7841: Editor: "Dirty" water heights are saved in modified CELLs Bug #7859: AutoCalc flag is not used to calculate potion value + Bug #7861: OpenMW-CS: Incorrect DIAL's type in INFO records Bug #7872: Region sounds use wrong odds Bug #7887: Editor: Mismatched reported script data size and actual data size causes a crash during save Bug #7898: Editor: Invalid reference scales are allowed diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 82135e0042..77effc3a5c 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -135,7 +135,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform(int stage, Messages& messages if (topic.mState == CSMWorld::RecordBase::State_Deleted) { // if the topic is deleted, we do not need to bother with INFO records. - ESM::Dialogue dialogue = topic.get(); + const ESM::Dialogue& dialogue = topic.get(); writer.startRecord(dialogue.sRecordId); dialogue.save(writer, true); writer.endRecord(dialogue.sRecordId); @@ -187,6 +187,7 @@ void CSMDoc::WriteDialogueCollectionStage::perform(int stage, Messages& messages { ESM::DialInfo info = record.get(); info.mId = record.get().mOriginalId; + info.mData.mType = topic.get().mType; if (iter == infos.begin()) info.mPrev = ESM::RefId(); From 4607722ce90310eccb206113ceacc4526f20a2d4 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 31 Mar 2024 11:37:02 +0200 Subject: [PATCH 332/451] Increment API version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5263d849e8..2bffdba34e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,7 +81,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 58) +set(OPENMW_LUA_API_REVISION 59) set(OPENMW_POSTPROCESSING_API_REVISION 1) set(OPENMW_VERSION_COMMITHASH "") From 7a291e502518d180b34076fbd1e0b894d47d989a Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 27 Mar 2024 17:50:55 -0500 Subject: [PATCH 333/451] FIX(CS): Re-add gold value column for objects --- apps/opencs/model/world/columns.cpp | 1 + apps/opencs/model/world/columns.hpp | 2 ++ apps/opencs/model/world/refidcollection.cpp | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 570e4134c1..bdbb8f697c 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -57,6 +57,7 @@ namespace CSMWorld { ColumnId_Charges, "Charges" }, { ColumnId_Enchantment, "Enchantment" }, { ColumnId_StackCount, "Count" }, + { ColumnId_GoldValue, "Value" }, { ColumnId_Teleport, "Teleport" }, { ColumnId_TeleportCell, "Teleport Cell" }, { ColumnId_IsLocked, "Locked" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 066f6395aa..3c4bff07f6 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -355,6 +355,8 @@ namespace CSMWorld ColumnId_ProjectileSpeed = 319, + ColumnId_GoldValue = 320, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 694f67e445..c3af3d4673 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -97,7 +97,7 @@ CSMWorld::RefIdCollection::RefIdCollection() inventoryColumns.mIcon = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Weight, ColumnBase::Display_Float); inventoryColumns.mWeight = &mColumns.back(); - mColumns.emplace_back(Columns::ColumnId_StackCount, ColumnBase::Display_Integer); + mColumns.emplace_back(Columns::ColumnId_GoldValue, ColumnBase::Display_Integer); inventoryColumns.mValue = &mColumns.back(); IngredientColumns ingredientColumns(inventoryColumns); From bb3c22e4a584ee550f394e2e1c13aa505ec71eab Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 1 Apr 2024 00:15:58 +0100 Subject: [PATCH 334/451] Add and register SettingValue stream operators --- components/config/gamesettings.cpp | 21 +++++++++++++++++++++ components/config/gamesettings.hpp | 3 +++ 2 files changed, 24 insertions(+) diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 21110562d5..a7da8fa150 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -24,6 +24,11 @@ namespace Config::GameSettings::GameSettings(const Files::ConfigurationManager& cfg) : mCfgMgr(cfg) { +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + // this needs calling once so Qt can see its stream operators, which it needs when dragging and dropping + // it's automatic with Qt 6 + qRegisterMetaTypeStreamOperators("Config::SettingValue"); +#endif } void Config::GameSettings::validatePaths() @@ -591,3 +596,19 @@ void Config::GameSettings::clear() mDataDirs.clear(); mDataLocal.clear(); } + +QDataStream& Config::operator<<(QDataStream& out, const SettingValue& settingValue) +{ + out << settingValue.value; + out << settingValue.originalRepresentation; + out << settingValue.context; + return out; +} + +QDataStream& Config::operator>>(QDataStream& in, SettingValue& settingValue) +{ + in >> settingValue.value; + in >> settingValue.originalRepresentation; + in >> settingValue.context; + return in; +} diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index d23f225eb0..7627d5153a 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -132,6 +132,9 @@ namespace Config static bool isOrderedLine(const QString& line); }; + + QDataStream& operator<<(QDataStream& out, const SettingValue& settingValue); + QDataStream& operator>>(QDataStream& in, SettingValue& settingValue); } Q_DECLARE_METATYPE(Config::SettingValue) From 36ca3b8bd923e826eff476a27729763dd71d6632 Mon Sep 17 00:00:00 2001 From: Arnaud Dochain Date: Mon, 1 Apr 2024 20:55:38 +0000 Subject: [PATCH 335/451] French localisation update --- files/data/l10n/Interface/fr.yaml | 2 +- files/data/l10n/OMWControls/fr.yaml | 72 ++++++++++++++++++++++++++++- files/data/l10n/OMWEngine/fr.yaml | 67 ++++++++++++++------------- files/data/l10n/OMWShaders/fr.yaml | 1 + 4 files changed, 107 insertions(+), 35 deletions(-) diff --git a/files/data/l10n/Interface/fr.yaml b/files/data/l10n/Interface/fr.yaml index bac4346364..0a078a1676 100644 --- a/files/data/l10n/Interface/fr.yaml +++ b/files/data/l10n/Interface/fr.yaml @@ -22,4 +22,4 @@ None: "Aucun" OK: "Valider" Cancel: "Annuler" Close: "Fermer" -#Copy: "Copy" +Copy: "Copier" diff --git a/files/data/l10n/OMWControls/fr.yaml b/files/data/l10n/OMWControls/fr.yaml index dab9ddb8fc..95fa88a6ac 100644 --- a/files/data/l10n/OMWControls/fr.yaml +++ b/files/data/l10n/OMWControls/fr.yaml @@ -9,11 +9,79 @@ alwaysRunDescription: | Inactif : Le personnage se déplace par défaut en marchant.\n\n La touche Maj. inverse temporairement ce paramètre.\n\n La touche Verr Maj inverse ce paramètre lorsqu'elle est verrouillée. + toggleSneak: "Mode discrétion maintenu" toggleSneakDescription: | Une simple pression de la touche associée (Ctrl par défaut) active le mode discrétion, une seconde pression désactive le mode discrétion.\n\n Il n'est plus nécessaire de maintenir une touche appuyée pour que le mode discrétion soit actif.\n\n Certains joueurs ayant une utilisation intensive du mode discrétion considèrent qu'il est plus aisé de contrôler leur personnage ainsi. -# smoothControllerMovement -# smoothControllerMovementDescription +smoothControllerMovement: "Mouvements à la manette adoucis" +smoothControllerMovementDescription: | + Active le lissage des mouvements du stick analogique. Ceci permet une transition entre marche et course moins abrupte. + +TogglePOV_name: "Changer de vue" +TogglePOV_description: "Change entre la vue à la première et la troisième personne. Maintenir la touche pour activer le mode attente." + +Zoom3rdPerson_name: "Zoom Avant/Arrière" +Zoom3rdPerson_description: "Approche / éloigne la caméra lorsque la caméra est à la troisième personne." + +MoveForward_name: "En avant" +MoveForward_description: "Annulé par En arrière." + +MoveBackward_name: "En arrière" +MoveBackward_description: "Annulé par En avant." + +MoveLeft_name: "À gauche" +MoveLeft_description: "Annulé par Droite." + +MoveRight_name: "À droite" +MoveRight_description: "Annulé par Gauche." + +Use_name: "Utiliser" +Use_description: "Attaque avec une arme ou lance une magie, dépend de la préparation du joueur." + +Run_name: "Courir" +Run_description: "Maintenir pour courir/marcher, en fonction du paramètre Toujours Courir." + +AlwaysRun_name: "Toujours courir" +AlwaysRun_description: "Active le paramètre Toujours courir." + +Jump_name: "Sauter" +Jump_description: "Saute lorsque le joueur touche le sol." + +AutoMove_name: "Course automatique" +AutoMove_description: "Active un mouvement continu vers l'avant." + +Sneak_name: "Discrétion" +Sneak_description: "Maintenez la touche si le paramètre Mode discrétion maintenu est désactivé." + +ToggleSneak_name: "Maintenir la discrétion" +ToggleSneak_description: "Entre en mode discrétion si le paramètre Mode discrétion maintenu est activé." + +ToggleWeapon_name: "Préparer arme" +ToggleWeapon_description: "Entre ou sort de la position armée." + +ToggleSpell_name: "Préparer sort" +ToggleSpell_description: "Entre ou sort de la position lanceur de sort." + +Inventory_name: "Inventaire" +Inventory_description: "Ouvre l'inventaire." + +Journal_name: "Journal" +Journal_description: "Ouvre le journal." + +QuickKeysMenu_name: "Menu Touches rapides" +QuickKeysMenu_description: "Ouvre le menu des touches rapides." + +SmoothMoveForward_name: "En avant, doux" +SmoothMoveForward_description: "Déplace le joueur en avant, avec une transition douce entre la marche et la course." + +SmoothMoveBackward_name: "En arrière, doux" +SmoothMoveBackward_description: "Déplace le joueur en arrière, avec une transition douce entre la marche et la course." + +SmoothMoveLeft_name: "À gauche, doux" +SmoothMoveLeft_description: "Déplace le joueur à gauche, avec une transition douce entre la marche et la course.." + +SmoothMoveRight_name: "À droite, doux" +SmoothMoveRight_description: "Déplace le joueur à droite, avec une transition douce entre la marche et la course." diff --git a/files/data/l10n/OMWEngine/fr.yaml b/files/data/l10n/OMWEngine/fr.yaml index 990ecfce9d..814314ff5a 100644 --- a/files/data/l10n/OMWEngine/fr.yaml +++ b/files/data/l10n/OMWEngine/fr.yaml @@ -2,11 +2,11 @@ ConsoleWindow: "Console" - # Debug window DebugWindow: "Fenêtre de débogage" LogViewer: "Journal" +LuaProfiler: "Profileur Lua" PhysicsProfiler: "Profileur des performances de la physique" @@ -22,22 +22,19 @@ LoadingInProgress: "Chargement de la sauvegarde" LoadingRequiresNewVersionError: |- Ce fichier de sauvegarde provient d'une version plus récente d'OpenMW, il n'est par consequent pas supporté. Mettez à jour votre version d'OpenMW afin de pouvoir charger cette sauvegarde. -# LoadingRequiresOldVersionError: |- -# This save file was created using an older version of OpenMW in a format that is no longer supported. -# Load and save this file using {version} to upgrade it. +LoadingRequiresOldVersionError: |- + Ce fichier de sauvegarde provient d'une version trop ancienne d'OpenMW, il n'est par consequent pas supporté. + Ouvrez et enregistez cette sauvegarde avec {version} pour la mettre à jour. NewGameConfirmation: "Voulez-vous démarrer une nouvelle partie ? Toute progression non sauvegardée sera perdue." QuitGameConfirmation: "Quitter la partie ?" SaveGameDenied: "Sauvegarde impossible" SavingInProgress: "Sauvegarde en cours..." -#ScreenshotFailed: "Failed to save screenshot" -#ScreenshotMade: "%s has been saved" +ScreenshotFailed: "Échec de la sauvegarde de la capture d'écran" +ScreenshotMade: "Capture d'écran %s sauvegardée" # Save game menu -SelectCharacter: "Sélection du personnage..." -TimePlayed: "Temps de jeu" - DeleteGame: "Supprimer la partie" DeleteGameConfirmation: "Voulez-vous réellement supprimer cette partie sauvegardée ?" EmptySaveNameError: "Impossible de sauvegarder une partie lui donner un nom !" @@ -46,19 +43,21 @@ MissingContentFilesConfirmation: |- Les données de jeu actuellement sélectionnées ne correspondent pas à celle indiquée dans cette sauvegarde. Cela peut entraîner des erreurs lors du chargement, mais aussi lors de votre partie. Voulez-vous continuer ? -#MissingContentFilesList: |- -# {files, plural, -# one{\n\nFound missing file: } -# few{\n\nFound {files} missing files:\n} -# other{\n\nFound {files} missing files:\n} -# } -#MissingContentFilesListCopy: |- -# {files, plural, -# one{\n\nPress Copy to place its name to the clipboard.} -# few{\n\nPress Copy to place their names to the clipboard.} -# other{\n\nPress Copy to place their names to the clipboard.} -# } +MissingContentFilesList: |- + {files, plural, + one{\n\nUn fichier manquant trouvé : } + few{\n\n{files} fichiers manquant trouvés :\n} + other{\n\n{files} fichiers manquant trouvés :\n} + } +MissingContentFilesListCopy: |- + {files, plural, + one{\n\nCliquez sur Copier pour placer ce nom dans le presse-papier.} + few{\n\nCliquez sur Copier pour placer ces noms dans le presse-papier.} + other{\n\nCliquez sur Copier pour placer ces noms dans le presse-papier.} + } OverwriteGameConfirmation: "Écraser la sauvegarde précédente ?" +SelectCharacter: "Sélection du personnage..." +TimePlayed: "Temps de jeu" # Settings menu @@ -100,31 +99,31 @@ InvertXAxis: "Inverser l'axe X" InvertYAxis: "Inverser l'axe Y" Language: "Localisation" LanguageNote: "Note: Ce paramètre n'affecte pas les textes des fichiers ESM." -LightingMethodLegacy: "Traditionnelle" LightingMethod: "Méthode d'affichage des lumières" -LightingMethodShadersCompatibility: "Shaders (mode de compatibilité)" +LightingMethodLegacy: "Traditionnelle" LightingMethodShaders: "Shaders" +LightingMethodShadersCompatibility: "Shaders (mode de compatibilité)" LightingResetToDefaults: "Voulez-vous réinitialiser les paramètres d'affichage des lumières à leur valeur par défaut ? Ces changements requièrent un redémarrage de l'application." +Lights: "Sources lumineuses" LightsBoundingSphereMultiplier: "Multiplicateur de sphère englobante" LightsBoundingSphereMultiplierTooltip: "valeur par défaut: 1.65\nMultiplicateur pour le rayon de la sphère incluant les sources lumineuses.\nUn multiplicateur plus élevé permet une extinction plus douce, mais applique un plus grand nombre de sources lumineuses sur chaque objet.\n\nCe paramètre ne modifie ni l'intensité ni la luminance des lumières." LightsFadeStartMultiplier: "Seuil de perte d'éclat lumineux" LightsFadeStartMultiplierTooltip: "valeur par défaut: 0.85\nFraction de la distance maximale d'une source à partir de laquelle l'intensité lumineuse commence à décroître.\n\nSélectionnez une valeur basse pour une transition douce ou une valeur plus élevée pour une transition plus abrupte." -#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n -# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n -# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n -# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." +LightsLightingMethodTooltip: "Définit la gestion des sources lumineuses :\n\n + \"Traditionnelle\" Chaque objet est éclairé par 8 sources lumineuses. Cet méthode est la plus proche du jeu original.\n\n + \"Shaders (mode de compatibilité)\" supprime la limite des 8 sources lumineuses. Cette méthode permet d'éclairer la végétation au sol, mais aussi de configurer à quel distance une source lumineuse s'estompe. Ce choix est recommandé pour les ordinateurs plus anciens avec un nombre de sources lumineuses proche de 8.\n\n + \"Shaders\" offre tous les bénéfices apportés par \"Shaders (mode de compatibilité)\", mais utilise une approche moderne. Celle-ci permet, sur du matériel moderne, d'augmenter le nombre de sources lumineuses par objet sans perte de performance." LightsMaximumDistance: "Distance maximale des sources lumineuses" LightsMaximumDistanceTooltip: "valeur par défaut: 8192\nDistance maximale d'affichage des sources lumineuses (en unité de distance).\n\nMettez cette valeur à 0 pour une distance d'affichage infinie." LightsMinimumInteriorBrightness: "Luminosité intérieure minimale" LightsMinimumInteriorBrightnessTooltip: "valeur par défaut: 0.08\nLuminosité ambiante minimum en intérieur.\n\nAugmentez cette valeur si les intérieurs vous semblent trop sombres." -Lights: "Sources lumineuses" MaxLights: "Maximum de sources lumineuses" MaxLightsTooltip: "valeur par défaut: 8\nNombre maximum de sources lumineuses par objet.\n\nUne valeur faible mène à des apparitions tardives des sources lumineuses similaires à celles obtenues avec la méthode d'éclairage traditionnelle." MenuHelpDelay: "Délai d'affichage du menu d'aide" MenuTransparency: "Transparence des menus" MouseAndKeyboard: "Souris/Clavier" -PostProcessingIsNotEnabled: "Le post-traitement est désactivé" PostProcessing: "Post-traitement" +PostProcessingIsNotEnabled: "Le post-traitement est désactivé" PostProcessingTooltip: "Modifiable dans le HUD de post-traitement, accessible à partir les paramètres de contrôle." Preferences: "Préférences" PrimaryLanguage: "Localisation principale" @@ -147,19 +146,20 @@ ReflectionShaderDetailWorld: "Monde" Refraction: "Réfraction" ResetControls: "Réinitialiser les contrôles" Screenshot: "Capture d'écran" -ScriptsDisabled: "Chargez une sauvegarde pour accéder aux paramètres des scripts." Scripts: "Scripts" +ScriptsDisabled: "Chargez une sauvegarde pour accéder aux paramètres des scripts." SecondaryLanguage: "Localisation secondaire" SecondaryLanguageTooltip: "Localisation utilisée si le texte est absent de la localisation principale." SensitivityHigh: "Haute" SensitivityLow: "Faible" SettingsWindow: "Options" Subtitles: "Sous-titres" +SunlightScattering: "Diffusion des rayons solaires" TestingExteriorCells: "Vérification des espaces (cells) extérieurs" TestingInteriorCells: "Vérification des espaces (cells) intérieurs" +TextureFiltering: "Filtre appliqué aux textures" TextureFilteringBilinear: "Bilinéaire" TextureFilteringDisabled: "Aucun" -TextureFiltering: "Filtre appliqué aux textures" TextureFilteringOther: "Autre" TextureFilteringTrilinear: "Trilinéaire" ToggleHUD: "Afficher/masquer le HUD" @@ -169,11 +169,14 @@ TransparencyNone: "Basse" Video: "Graphisme" ViewDistance: "Distance d'affichage" VSync: "VSync" +VSyncAdaptive: "Adaptif" Water: "Eau" WaterShader: "Shader pour l'eau" WaterShaderTextureQuality: "Qualité des textures" WindowBorder: "Bordure de fenêtre" -WindowModeFullscreen: "Plein écran" WindowMode: "Mode d'affichage" +WindowModeFullscreen: "Plein écran" +WindowModeHint: "Info : Le mode \"Fenêtré plein écran\" utilise toujours la résolution native de l'écran." WindowModeWindowed: "Fenêtré" WindowModeWindowedFullscreen: "Fenêtré plein écran" +WobblyShores: "Rivages vacillants" diff --git a/files/data/l10n/OMWShaders/fr.yaml b/files/data/l10n/OMWShaders/fr.yaml index 3b4e47370e..45513589eb 100644 --- a/files/data/l10n/OMWShaders/fr.yaml +++ b/files/data/l10n/OMWShaders/fr.yaml @@ -34,6 +34,7 @@ DisplayDepthFactorName: "Rapport couleur/profondeur" DisplayDepthFactorDescription: "Détermine la corrélation entre la valeur de profondeur d'un pixel et sa couleur de sortie. Une valeur élevée mène à une image plus lumineuse." DisplayDepthName: "Visualise le Z-buffer (tampon de profondeur)." DisplayNormalsName: "Visualise le G-buffer de normales (normals pass)" +NormalsInWorldSpace: "Affiche la normale de la surface de chaque objet" ContrastLevelDescription: "Niveau de contraste" ContrastLevelName: "Contraste" GammaLevelDescription: "Correction Gamma" From b3188b593cc61af3e68ff7932b6f713c67244fe9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Mar 2024 15:20:24 +0400 Subject: [PATCH 336/451] Disable MyGUI windows snapping --- files/data/mygui/openmw_windows.skin.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/files/data/mygui/openmw_windows.skin.xml b/files/data/mygui/openmw_windows.skin.xml index 14f6930b3c..9491862b34 100644 --- a/files/data/mygui/openmw_windows.skin.xml +++ b/files/data/mygui/openmw_windows.skin.xml @@ -457,7 +457,6 @@ - @@ -592,7 +591,6 @@ - @@ -729,7 +727,6 @@ - From 939760007e37b06a9d05445ee7ca400b59446941 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Mar 2024 15:20:53 +0400 Subject: [PATCH 337/451] Resize console window on resolution change, not reset it --- apps/openmw/mwgui/console.cpp | 5 ----- apps/openmw/mwgui/console.hpp | 2 -- 2 files changed, 7 deletions(-) diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index b430e08142..a188f3c86b 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -771,11 +771,6 @@ namespace MWGui return output.append(matches.front()); } - void Console::onResChange(int width, int height) - { - setCoord(10, 10, width - 10, height / 2); - } - void Console::updateSelectedObjectPtr(const MWWorld::Ptr& currentPtr, const MWWorld::Ptr& newPtr) { if (mPtr == currentPtr) diff --git a/apps/openmw/mwgui/console.hpp b/apps/openmw/mwgui/console.hpp index 79d18847a4..2b6ecfc8ad 100644 --- a/apps/openmw/mwgui/console.hpp +++ b/apps/openmw/mwgui/console.hpp @@ -47,8 +47,6 @@ namespace MWGui void onOpen() override; - void onResChange(int width, int height) override; - // Print a message to the console, in specified color. void print(const std::string& msg, std::string_view color = MWBase::WindowManager::sConsoleColor_Default); From 1b544b93d25149a375791949cc7f54fcacd654a7 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 13 Mar 2024 16:07:29 +0400 Subject: [PATCH 338/451] Do not allow to move resizable windows outside of game window --- apps/openmw/mwgui/inventorywindow.cpp | 2 ++ apps/openmw/mwgui/windowbase.cpp | 29 ++++++++++++++++++++++++++ apps/openmw/mwgui/windowbase.hpp | 2 ++ apps/openmw/mwgui/windowmanagerimp.cpp | 12 ++++++++--- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 0fbd15dda2..4805f7f3cb 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -417,6 +417,8 @@ namespace MWGui void InventoryWindow::onWindowResize(MyGUI::Window* _sender) { + WindowBase::clampWindowCoordinates(_sender); + adjustPanes(); const WindowSettingValues settings = getModeSettings(mGuiMode); diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index a680e38cf8..eff5c98588 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -7,6 +7,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include #include #include "draganddrop.hpp" @@ -77,6 +78,34 @@ void WindowBase::center() mMainWidget->setCoord(coord); } +void WindowBase::clampWindowCoordinates(MyGUI::Window* window) +{ + auto minSize = window->getMinSize(); + minSize.height = static_cast(minSize.height * Settings::gui().mScalingFactor); + minSize.width = static_cast(minSize.width * Settings::gui().mScalingFactor); + + // Window's minimum size is larger than the screen size, can not clamp coordinates + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + if (minSize.width > viewSize.width || minSize.height > viewSize.height) + return; + + int left = std::max(0, window->getPosition().left); + int top = std::max(0, window->getPosition().top); + int width = std::clamp(window->getSize().width, 0, viewSize.width); + int height = std::clamp(window->getSize().height, 0, viewSize.height); + if (left + width > viewSize.width) + left = viewSize.width - width; + + if (top + height > viewSize.height) + top = viewSize.height - height; + + if (window->getSize().width != width || window->getSize().height != height) + window->setSize(width, height); + + if (window->getPosition().left != left || window->getPosition().top != top) + window->setPosition(left, top); +} + WindowModal::WindowModal(const std::string& parLayout) : WindowBase(parLayout) { diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 54fb269305..466060c6ad 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -52,6 +52,8 @@ namespace MWGui virtual std::string_view getWindowIdForLua() const { return ""; } void setDisabledByLua(bool disabled) { mDisabledByLua = disabled; } + static void clampWindowCoordinates(MyGUI::Window* window); + protected: virtual void onTitleDoubleClicked(); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 0105d7c1ba..61a8b3361c 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1200,6 +1200,8 @@ namespace MWGui const WindowRectSettingValues& rect = settings.mIsMaximized ? settings.mMaximized : settings.mRegular; window->setPosition(MyGUI::IntPoint(static_cast(rect.mX * x), static_cast(rect.mY * y))); window->setSize(MyGUI::IntSize(static_cast(rect.mW * x), static_cast(rect.mH * y))); + + WindowBase::clampWindowCoordinates(window); } for (const auto& window : mWindows) @@ -1714,13 +1716,15 @@ namespace MWGui const WindowRectSettingValues& rect = settings.mIsMaximized ? settings.mMaximized : settings.mRegular; - layout->mMainWidget->setPosition( + MyGUI::Window* window = layout->mMainWidget->castType(); + window->setPosition( MyGUI::IntPoint(static_cast(rect.mX * viewSize.width), static_cast(rect.mY * viewSize.height))); - layout->mMainWidget->setSize( + window->setSize( MyGUI::IntSize(static_cast(rect.mW * viewSize.width), static_cast(rect.mH * viewSize.height))); - MyGUI::Window* window = layout->mMainWidget->castType(); window->eventWindowChangeCoord += MyGUI::newDelegate(this, &WindowManager::onWindowChangeCoord); + WindowBase::clampWindowCoordinates(window); + mTrackedWindows.emplace(window, settings); } @@ -1750,6 +1754,8 @@ namespace MWGui if (it == mTrackedWindows.end()) return; + WindowBase::clampWindowCoordinates(window); + const WindowSettingValues& settings = it->second; MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); From 02e000ab5b1c817a15e5b52665838c8bd7cb2733 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 2 Apr 2024 11:56:30 +0400 Subject: [PATCH 339/451] Add changelog entries --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f20ecf8bb8..587e5e7a52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -221,6 +221,8 @@ Feature #7795: Support MaxNumberRipples INI setting Feature #7805: Lua Menu context Feature #7860: Lua: Expose NPC AI settings (fight, alarm, flee) + Feature #7875: Disable MyGUI windows snapping + Feature #7914: Do not allow to move GUI windows out of screen Task #5896: Do not use deprecated MyGUI properties Task #6085: Replace boost::filesystem with std::filesystem Task #6149: Dehardcode Lua API_REVISION From 3b930e44716004045b57c291866502442b8df46c Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 3 Apr 2024 07:12:53 +0000 Subject: [PATCH 340/451] Restore !613 --- apps/opencs/model/filter/parser.cpp | 5 ++- apps/opencs/model/filter/textnode.hpp | 2 ++ apps/opencs/model/prefs/state.cpp | 1 + apps/opencs/model/prefs/values.hpp | 1 + apps/opencs/model/world/idtableproxymodel.cpp | 35 ++++++++++++++++--- apps/opencs/model/world/idtableproxymodel.hpp | 9 +++++ 6 files changed, 48 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/filter/parser.cpp b/apps/opencs/model/filter/parser.cpp index aadad5f8f5..6248b03bb4 100644 --- a/apps/opencs/model/filter/parser.cpp +++ b/apps/opencs/model/filter/parser.cpp @@ -452,7 +452,10 @@ std::shared_ptr CSMFilter::Parser::parseText() return std::shared_ptr(); } - return std::make_shared(columnId, text); + auto node = std::make_shared(columnId, text); + if (!node->isValid()) + error(); + return node; } std::shared_ptr CSMFilter::Parser::parseValue() diff --git a/apps/opencs/model/filter/textnode.hpp b/apps/opencs/model/filter/textnode.hpp index 14efa0a3a0..d629cbe336 100644 --- a/apps/opencs/model/filter/textnode.hpp +++ b/apps/opencs/model/filter/textnode.hpp @@ -34,6 +34,8 @@ namespace CSMFilter ///< Return a string that represents this node. /// /// \param numericColumns Use numeric IDs instead of string to represent columns. + + bool isValid() { return mRegExp.isValid(); } }; } diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index c11996a6ea..ba3a544f36 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -90,6 +90,7 @@ void CSMPrefs::State::declare() .setTooltip( "When editing a record, open the view in a new window," " rather than docked in the main view."); + declareInt(mValues->mIdTables.mFilterDelay, "Delay before applying a filter (in miliseconds)"); declareCategory("ID Dialogues"); declareBool(mValues->mIdDialogues.mToolbar, "Show toolbar"); diff --git a/apps/opencs/model/prefs/values.hpp b/apps/opencs/model/prefs/values.hpp index 9899a239a9..d3f78e5d01 100644 --- a/apps/opencs/model/prefs/values.hpp +++ b/apps/opencs/model/prefs/values.hpp @@ -138,6 +138,7 @@ namespace CSMPrefs EnumSettingValue mJumpToAdded{ mIndex, sName, "jump-to-added", sJumpAndSelectValues, 0 }; Settings::SettingValue mExtendedConfig{ mIndex, sName, "extended-config", false }; Settings::SettingValue mSubviewNewWindow{ mIndex, sName, "subview-new-window", false }; + Settings::SettingValue mFilterDelay{ mIndex, sName, "filter-delay", 500 }; }; struct IdDialoguesCategory : Settings::WithIndex diff --git a/apps/opencs/model/world/idtableproxymodel.cpp b/apps/opencs/model/world/idtableproxymodel.cpp index d4e342f5de..c03b4ea30a 100644 --- a/apps/opencs/model/world/idtableproxymodel.cpp +++ b/apps/opencs/model/world/idtableproxymodel.cpp @@ -63,9 +63,18 @@ bool CSMWorld::IdTableProxyModel::filterAcceptsRow(int sourceRow, const QModelIn CSMWorld::IdTableProxyModel::IdTableProxyModel(QObject* parent) : QSortFilterProxyModel(parent) + , mFilterTimer{ new QTimer(this) } , mSourceModel(nullptr) { setSortCaseSensitivity(Qt::CaseInsensitive); + + mFilterTimer->setSingleShot(true); + int intervalSetting = CSMPrefs::State::get()["ID Tables"]["filter-delay"].toInt(); + mFilterTimer->setInterval(intervalSetting); + + connect(&CSMPrefs::State::get(), &CSMPrefs::State::settingChanged, this, + [this](const CSMPrefs::Setting* setting) { this->settingChanged(setting); }); + connect(mFilterTimer.get(), &QTimer::timeout, this, [this]() { this->timerTimeout(); }); } QModelIndex CSMWorld::IdTableProxyModel::getModelIndex(const std::string& id, int column) const @@ -87,10 +96,8 @@ void CSMWorld::IdTableProxyModel::setSourceModel(QAbstractItemModel* model) void CSMWorld::IdTableProxyModel::setFilter(const std::shared_ptr& filter) { - beginResetModel(); - mFilter = filter; - updateColumnMap(); - endResetModel(); + mAwaitingFilter = filter; + mFilterTimer->start(); } bool CSMWorld::IdTableProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const @@ -131,6 +138,26 @@ void CSMWorld::IdTableProxyModel::refreshFilter() } } +void CSMWorld::IdTableProxyModel::timerTimeout() +{ + if (mAwaitingFilter) + { + beginResetModel(); + mFilter = mAwaitingFilter; + updateColumnMap(); + endResetModel(); + mAwaitingFilter.reset(); + } +} + +void CSMWorld::IdTableProxyModel::settingChanged(const CSMPrefs::Setting* setting) +{ + if (*setting == "ID Tables/filter-delay") + { + mFilterTimer->setInterval(setting->toInt()); + } +} + void CSMWorld::IdTableProxyModel::sourceRowsInserted(const QModelIndex& parent, int /*start*/, int end) { refreshFilter(); diff --git a/apps/opencs/model/world/idtableproxymodel.hpp b/apps/opencs/model/world/idtableproxymodel.hpp index 639cf47287..b7d38f5a2d 100644 --- a/apps/opencs/model/world/idtableproxymodel.hpp +++ b/apps/opencs/model/world/idtableproxymodel.hpp @@ -10,6 +10,9 @@ #include #include #include +#include + +#include "../prefs/state.hpp" #include "columns.hpp" @@ -29,6 +32,8 @@ namespace CSMWorld Q_OBJECT std::shared_ptr mFilter; + std::unique_ptr mFilterTimer; + std::shared_ptr mAwaitingFilter; std::map mColumnMap; // column ID, column index in this model (or -1) // Cache of enum values for enum columns (e.g. Modified, Record Type). @@ -68,6 +73,10 @@ namespace CSMWorld virtual void sourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + void timerTimeout(); + + void settingChanged(const CSMPrefs::Setting* setting); + signals: void rowAdded(const std::string& id); From 5e99545b9c5fd111218c60af6271e0f5e75df1fb Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 3 Apr 2024 21:11:49 +0300 Subject: [PATCH 341/451] Don't discard stagger/KO animation movement --- apps/openmw/mwmechanics/character.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c847bae033..f7c381d33e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2698,6 +2698,9 @@ namespace MWMechanics bool CharacterController::isMovementAnimationControlled() const { + if (mHitState != CharState_None) + return true; + if (Settings::game().mPlayerMovementIgnoresAnimation && mPtr == getPlayer()) return false; From dfdd7aa684a02594f2013ad9460e1c0f8fc9b2d1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 3 Apr 2024 21:16:47 +0300 Subject: [PATCH 342/451] Always queue movement even when there's none --- apps/openmw/mwmechanics/character.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index f7c381d33e..646cee8a23 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2479,9 +2479,7 @@ namespace MWMechanics movement.x() *= scale; movement.y() *= scale; - // Update movement - if (movement != osg::Vec3f()) - world->queueMovement(mPtr, movement); + world->queueMovement(mPtr, movement); } mSkipAnim = false; From 2288a691d28ee6df2cd2d386a345a65c18002f9b Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Apr 2024 00:10:51 +0100 Subject: [PATCH 343/451] Replace osgAnimation bone underscore naming at load time, map bone instances, reset root bone transform each frame --- CHANGELOG.md | 1 + apps/openmw/mwrender/animation.cpp | 24 ++++++-- apps/openmw/mwrender/animation.hpp | 1 + components/misc/strings/algorithm.hpp | 7 +++ components/resource/keyframemanager.cpp | 27 +++++---- components/resource/scenemanager.cpp | 79 ++++++++++++++++++++++++- components/sceneutil/extradata.cpp | 7 ++- components/sceneutil/osgacontroller.cpp | 53 +++++++++++++++++ components/sceneutil/osgacontroller.hpp | 3 + components/sceneutil/visitor.cpp | 49 ++++----------- components/sceneutil/visitor.hpp | 20 +++++++ 11 files changed, 211 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f20ecf8bb8..6c223026b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ Bug #6657: Distant terrain tiles become black when using FWIW mod Bug #6661: Saved games that have no preview screenshot cause issues or crashes Bug #6716: mwscript comparison operator handling is too restrictive + Bug #6723: "Turn to movement direction" makes the player rotate wildly with COLLADA Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW Bug #6758: Main menu background video can be stopped by opening the options menu Bug #6807: Ultimate Galleon is not working properly diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 0baf68ed5d..1807b0050f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -530,6 +530,7 @@ namespace MWRender , mHasMagicEffects(false) , mAlpha(1.f) , mPlayScriptedOnly(false) + , mRequiresBoneMap(false) { for (size_t i = 0; i < sNumBlendMasks; i++) mAnimationTimePtr[i] = std::make_shared(); @@ -964,8 +965,17 @@ namespace MWRender { if (!mNodeMapCreated && mObjectRoot) { - SceneUtil::NodeMapVisitor visitor(mNodeMap); - mObjectRoot->accept(visitor); + // If the base of this animation is an osgAnimation, we should map the bones not matrix transforms + if (mRequiresBoneMap) + { + SceneUtil::NodeMapVisitorBoneOnly visitor(mNodeMap); + mObjectRoot->accept(visitor); + } + else + { + SceneUtil::NodeMapVisitor visitor(mNodeMap); + mObjectRoot->accept(visitor); + } mNodeMapCreated = true; } return mNodeMap; @@ -1447,10 +1457,9 @@ namespace MWRender } } + osg::ref_ptr created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); if (!forceskeleton) { - osg::ref_ptr created - = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); mInsert->addChild(created); mObjectRoot = created->asGroup(); if (!mObjectRoot) @@ -1466,8 +1475,6 @@ namespace MWRender } else { - osg::ref_ptr created - = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); osg::ref_ptr skel = dynamic_cast(created.get()); if (!skel) { @@ -1479,6 +1486,10 @@ namespace MWRender mInsert->addChild(mObjectRoot); } + // osgAnimation formats with skeletons should have their nodemap be bone instances + // FIXME: better way to detect osgAnimation here instead of relying on extension? + mRequiresBoneMap = mSkeleton != nullptr && !Misc::StringUtils::ciEndsWith(model, "nif"); + if (previousStateset) mObjectRoot->setStateSet(previousStateset); @@ -1791,6 +1802,7 @@ namespace MWRender osg::ref_ptr controller(new RotateController(mObjectRoot.get())); node->addUpdateCallback(controller); mActiveControllers.emplace_back(node, controller); + return controller; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 22b7167a9c..4cff658011 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -246,6 +246,7 @@ namespace MWRender osg::ref_ptr mLightListCallback; bool mPlayScriptedOnly; + bool mRequiresBoneMap; const NodeMap& getNodeMap() const; diff --git a/components/misc/strings/algorithm.hpp b/components/misc/strings/algorithm.hpp index 28bc696cd3..2bf7125c8b 100644 --- a/components/misc/strings/algorithm.hpp +++ b/components/misc/strings/algorithm.hpp @@ -15,6 +15,13 @@ namespace Misc::StringUtils bool operator()(char x, char y) const { return toLower(x) < toLower(y); } }; + inline std::string underscoresToSpaces(const std::string_view& oldName) + { + std::string newName(oldName); + std::replace(newName.begin(), newName.end(), '_', ' '); + return newName; + } + inline bool ciLess(std::string_view x, std::string_view y) { return std::lexicographical_compare(x.begin(), x.end(), y.begin(), y.end(), CiCharLess()); diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 0cbbe40d60..67a434e47b 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -37,11 +37,11 @@ namespace Resource bool RetrieveAnimationsVisitor::belongsToLeftUpperExtremity(const std::string& name) { - static const std::array boneNames = { "bip01_l_clavicle", "left_clavicle", "bip01_l_upperarm", "left_upper_arm", - "bip01_l_forearm", "bip01_l_hand", "left_hand", "left_wrist", "shield_bone", "bip01_l_pinky1", - "bip01_l_pinky2", "bip01_l_pinky3", "bip01_l_ring1", "bip01_l_ring2", "bip01_l_ring3", "bip01_l_middle1", - "bip01_l_middle2", "bip01_l_middle3", "bip01_l_pointer1", "bip01_l_pointer2", "bip01_l_pointer3", - "bip01_l_thumb1", "bip01_l_thumb2", "bip01_l_thumb3", "left_forearm" }; + static const std::array boneNames = { "bip01 l clavicle", "left clavicle", "bip01 l upperarm", "left upper arm", + "bip01 l forearm", "bip01 l hand", "left hand", "left wrist", "shield bone", "bip01 l pinky1", + "bip01 l pinky2", "bip01 l pinky3", "bip01 l ring1", "bip01 l ring2", "bip01 l ring3", "bip01 l middle1", + "bip01 l middle2", "bip01 l middle3", "bip01 l pointer1", "bip01 l pointer2", "bip01 l pointer3", + "bip01 l thumb1", "bip01 l thumb2", "bip01 l thumb3", "left forearm" }; if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) return true; @@ -51,11 +51,11 @@ namespace Resource bool RetrieveAnimationsVisitor::belongsToRightUpperExtremity(const std::string& name) { - static const std::array boneNames = { "bip01_r_clavicle", "right_clavicle", "bip01_r_upperarm", - "right_upper_arm", "bip01_r_forearm", "bip01_r_hand", "right_hand", "right_wrist", "bip01_r_thumb1", - "bip01_r_thumb2", "bip01_r_thumb3", "weapon_bone", "bip01_r_pinky1", "bip01_r_pinky2", "bip01_r_pinky3", - "bip01_r_ring1", "bip01_r_ring2", "bip01_r_ring3", "bip01_r_middle1", "bip01_r_middle2", "bip01_r_middle3", - "bip01_r_pointer1", "bip01_r_pointer2", "bip01_r_pointer3", "right_forearm" }; + static const std::array boneNames = { "bip01 r clavicle", "right clavicle", "bip01 r upperarm", + "right upper arm", "bip01 r forearm", "bip01 r hand", "right hand", "right wrist", "bip01 r thumb1", + "bip01 r thumb2", "bip01 r thumb3", "weapon bone", "bip01 r pinky1", "bip01 r pinky2", "bip01 r pinky3", + "bip01 r ring1", "bip01 r ring2", "bip01 r ring3", "bip01 r middle1", "bip01 r middle2", "bip01 r middle3", + "bip01 r pointer1", "bip01 r pointer2", "bip01 r pointer3", "right forearm" }; if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) return true; @@ -66,7 +66,7 @@ namespace Resource bool RetrieveAnimationsVisitor::belongsToTorso(const std::string& name) { static const std::array boneNames - = { "bip01_spine1", "bip01_spine2", "bip01_neck", "bip01_head", "head", "neck", "chest", "groin" }; + = { "bip01 spine1", "bip01 spine2", "bip01 neck", "bip01 head", "head", "neck", "chest", "groin" }; if (std::find(boneNames.begin(), boneNames.end(), name) != boneNames.end()) return true; @@ -88,9 +88,7 @@ namespace Resource { //"Default" is osg dae plugin's default naming scheme for unnamed animations if (animation->getName() == "Default") - { animation->setName(std::string("idle")); - } osg::ref_ptr mergedAnimationTrack = new Resource::Animation; const std::string animationName = animation->getName(); @@ -99,6 +97,9 @@ namespace Resource const osgAnimation::ChannelList& channels = animation->getChannels(); for (const auto& channel : channels) { + // Repalce channel target name to match the renamed bones/transforms + channel->setTargetName(Misc::StringUtils::underscoresToSpaces(channel->getTargetName())); + if (name == "Bip01 R Clavicle") { if (!belongsToRightUpperExtremity(channel->getTargetName())) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e4d0b4363d..b8272c8b26 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -9,7 +9,10 @@ #include #include +#include #include +#include +#include #include @@ -353,6 +356,66 @@ namespace Resource std::vector> mRigGeometryHolders; }; + void updateVertexInfluenceMap(osgAnimation::RigGeometry& rig) + { + osgAnimation::VertexInfluenceMap* vertexInfluenceMap = rig.getInfluenceMap(); + if (!vertexInfluenceMap) + return; + + std::vector> renameList; + + // Collecting updates + for (const auto& influence : *vertexInfluenceMap) + { + const std::string& oldBoneName = influence.first; + std::string newBoneName = Misc::StringUtils::underscoresToSpaces(oldBoneName); + if (newBoneName != oldBoneName) + renameList.emplace_back(oldBoneName, newBoneName); + } + + // Applying updates (cant update map while iterating it!) + for (const auto& rename : renameList) + { + const std::string& oldName = rename.first; + const std::string& newName = rename.second; + + // Check if new name already exists to avoid overwriting + if (vertexInfluenceMap->find(newName) == vertexInfluenceMap->end()) + (*vertexInfluenceMap)[newName] = std::move((*vertexInfluenceMap)[oldName]); + + vertexInfluenceMap->erase(oldName); + } + } + + class RenameBonesVisitor : public osg::NodeVisitor + { + public: + RenameBonesVisitor() + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + { + } + + void apply(osg::MatrixTransform& node) override + { + node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); + + // osgAnimation update callback name must match bone name/channel targets + osg::Callback* cb = node.getUpdateCallback(); + while (cb) + { + osgAnimation::AnimationUpdateCallback* animCb + = dynamic_cast*>(cb); + + if (animCb) + animCb->setName(Misc::StringUtils::underscoresToSpaces(animCb->getName())); + + cb = cb->getNestedCallback(); + } + + traverse(node); + } + }; + SceneManager::SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager, double expiryDelay) : ResourceManager(vfs, expiryDelay) @@ -556,6 +619,7 @@ namespace Resource VFS::Path::NormalizedView normalizedFilename, std::istream& model, Resource::ImageManager* imageManager) { const std::string_view ext = Misc::getFileExtension(normalizedFilename.value()); + const bool isColladaFile = ext == "dae"; osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(std::string(ext)); if (!reader) { @@ -571,7 +635,7 @@ namespace Resource // findFileCallback would be necessary. but findFileCallback does not support virtual files, so we can't // implement it. options->setReadFileCallback(new ImageReadCallback(imageManager)); - if (ext == "dae") + if (isColladaFile) options->setOptionString("daeUseSequencedTextureUnits"); const std::array fileHash = Files::getHash(normalizedFilename.value(), model); @@ -599,9 +663,13 @@ namespace Resource node->accept(rigFinder); for (osg::Node* foundRigNode : rigFinder.mFoundNodes) { - if (foundRigNode->libraryName() == std::string("osgAnimation")) + if (foundRigNode->libraryName() == std::string_view("osgAnimation")) { osgAnimation::RigGeometry* foundRigGeometry = static_cast(foundRigNode); + + if (isColladaFile) + Resource::updateVertexInfluenceMap(*foundRigGeometry); + osg::ref_ptr newRig = new SceneUtil::RigGeometryHolder(*foundRigGeometry, osg::CopyOp::DEEP_COPY_ALL); @@ -616,13 +684,18 @@ namespace Resource } } - if (ext == "dae") + if (isColladaFile) { Resource::ColladaDescriptionVisitor colladaDescriptionVisitor; node->accept(colladaDescriptionVisitor); if (colladaDescriptionVisitor.mSkeleton) { + // Collada bones may have underscores in place of spaces due to a collada limitation + // we should rename the bones and update callbacks here at load time + Resource::RenameBonesVisitor renameBoneVisitor; + node->accept(renameBoneVisitor); + if (osg::Group* group = dynamic_cast(node)) { group->removeChildren(0, group->getNumChildren()); diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index bd82e9abba..8d024d5824 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -44,11 +45,15 @@ namespace SceneUtil void ProcessExtraDataVisitor::apply(osg::Node& node) { + // If an osgAnimation bone/transform, ensure underscores in name are replaced with spaces + // this is for compatibility reasons + if (node.libraryName() == std::string_view("osgAnimation") && node.className() == std::string_view("Bone")) + node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); + if (!mSceneMgr->getSoftParticles()) return; 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()) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 06b693a6ca..40b6640973 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -9,6 +10,7 @@ #include #include +#include #include #include #include @@ -24,6 +26,10 @@ namespace SceneUtil void LinkVisitor::link(osgAnimation::UpdateMatrixTransform* umt) { + // If osgAnimation had underscores, we should update the umt name also + // otherwise the animation channel and updates wont be applied + umt->setName(Misc::StringUtils::underscoresToSpaces(umt->getName())); + const osgAnimation::ChannelList& channels = mAnimation->getChannels(); for (const auto& channel : channels) { @@ -128,6 +134,47 @@ namespace SceneUtil return osg::Vec3f(); } + osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string& name) const + { + std::string animationName; + float newTime = time; + + // Find the correct animation based on time + for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations) + { + if (time >= emulatedAnimation.mStartTime && time <= emulatedAnimation.mStopTime) + { + newTime = time - emulatedAnimation.mStartTime; + animationName = emulatedAnimation.mName; + } + } + + // Find the bone's transform track in animation + for (const auto& mergedAnimationTrack : mMergedAnimationTracks) + { + if (mergedAnimationTrack->getName() != animationName) + continue; + + const osgAnimation::ChannelList& channels = mergedAnimationTrack->getChannels(); + + for (const auto& channel : channels) + { + if (!Misc::StringUtils::ciEqual(name, channel->getTargetName()) || channel->getName() != "transform") + continue; + + if (osgAnimation::MatrixLinearSampler* templateSampler + = dynamic_cast(channel->getSampler())) + { + osg::Matrixf matrix; + templateSampler->getValueAt(newTime, matrix); + return matrix; + } + } + } + + return osg::Matrixf::identity(); + } + void OsgAnimationController::update(float time, const std::string& animationName) { for (const auto& mergedAnimationTrack : mMergedAnimationTracks) @@ -162,6 +209,12 @@ namespace SceneUtil update(time - emulatedAnimation.mStartTime, emulatedAnimation.mName); } } + + // Reset the transform of this node to whats in the animation + // we force this here because downstream some code relies on the bone having a non-modified transform + // as this is how the NIF controller behaves. RotationController is a good example of this. + // Without this here, it causes osgAnimation skeletons to spin wildly + static_cast(node)->setMatrix(getTransformForNode(time, node->getName())); } traverse(node, nv); diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp index 8739d68b99..000580631c 100644 --- a/components/sceneutil/osgacontroller.hpp +++ b/components/sceneutil/osgacontroller.hpp @@ -59,6 +59,9 @@ namespace SceneUtil /// @brief Handles the location of the instance osg::Vec3f getTranslation(float time) const override; + /// @brief Handles finding bone position in the animation + osg::Matrixf getTransformForNode(float time, const std::string& name) const; + /// @brief Calls animation track update() void update(float time, const std::string& animationName); diff --git a/components/sceneutil/visitor.cpp b/components/sceneutil/visitor.cpp index ffcf12b167..8d1bf46322 100644 --- a/components/sceneutil/visitor.cpp +++ b/components/sceneutil/visitor.cpp @@ -5,6 +5,8 @@ #include +#include + #include #include @@ -13,7 +15,6 @@ namespace SceneUtil { - bool FindByNameVisitor::checkGroup(osg::Group& group) { if (Misc::StringUtils::ciEqual(group.getName(), mNameToFind)) @@ -22,35 +23,13 @@ namespace SceneUtil return true; } - // FIXME: can the nodes/bones be renamed at loading stage rather than each time? - // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses - // whitespace-separated names) - std::string nodeName = group.getName(); - std::replace(nodeName.begin(), nodeName.end(), '_', ' '); - if (Misc::StringUtils::ciEqual(nodeName, mNameToFind)) - { - mFoundNode = &group; - return true; - } return false; } void FindByClassVisitor::apply(osg::Node& node) { if (Misc::StringUtils::ciEqual(node.className(), mNameToFind)) - { mFoundNodes.push_back(&node); - } - else - { - // FIXME: can the nodes/bones be renamed at loading stage rather than each time? - // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses - // whitespace-separated names) - std::string nodeName = node.className(); - std::replace(nodeName.begin(), nodeName.end(), '_', ' '); - if (Misc::StringUtils::ciEqual(nodeName, mNameToFind)) - mFoundNodes.push_back(&node); - } traverse(node); } @@ -69,26 +48,22 @@ namespace SceneUtil void FindByNameVisitor::apply(osg::Geometry&) {} - void NodeMapVisitor::apply(osg::MatrixTransform& trans) + void NodeMapVisitorBoneOnly::apply(osg::MatrixTransform& trans) { - // Choose first found node in file - - if (trans.libraryName() == std::string_view("osgAnimation")) - { - std::string nodeName = trans.getName(); - - // FIXME: can the nodes/bones be renamed at loading stage rather than each time? - // Convert underscores to whitespaces as a workaround for Collada (OpenMW's animation system uses - // whitespace-separated names) - std::replace(nodeName.begin(), nodeName.end(), '_', ' '); - mMap.emplace(nodeName, &trans); - } - else + // Choose first found bone in file + if (dynamic_cast(&trans) != nullptr) mMap.emplace(trans.getName(), &trans); traverse(trans); } + void NodeMapVisitor::apply(osg::MatrixTransform& trans) + { + // Choose first found node in file + mMap.emplace(trans.getName(), &trans); + traverse(trans); + } + void RemoveVisitor::remove() { for (RemoveVec::iterator it = mToRemove.begin(); it != mToRemove.end(); ++it) diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index 3e3df4d4b3..a5af88d7de 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -70,6 +70,26 @@ namespace SceneUtil NodeMap& mMap; }; + /// Maps names to bone nodes + class NodeMapVisitorBoneOnly : public osg::NodeVisitor + { + public: + typedef std::unordered_map, Misc::StringUtils::CiHash, + Misc::StringUtils::CiEqual> + NodeMap; + + NodeMapVisitorBoneOnly(NodeMap& map) + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + , mMap(map) + { + } + + void apply(osg::MatrixTransform& trans) override; + + private: + NodeMap& mMap; + }; + /// @brief Base class for visitors that remove nodes from a scene graph. /// Subclasses need to fill the mToRemove vector. /// To use, node->accept(removeVisitor); removeVisitor.remove(); From ceabeab0fd106cbcaf9b33c9c36712e3a620d7c8 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Apr 2024 00:11:15 +0100 Subject: [PATCH 344/451] Fix RotateController not updating skeleton --- apps/openmw/mwrender/rotatecontroller.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index d7f8bb902c..73c4cddff5 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -1,6 +1,7 @@ #include "rotatecontroller.hpp" #include +#include namespace MWRender { @@ -40,9 +41,19 @@ namespace MWRender osg::Quat orient = worldOrient * mRotate * worldOrientInverse * matrix.getRotate(); matrix.setRotate(orient); matrix.setTrans(matrix.getTrans() + worldOrientInverse * mOffset); - node->setMatrix(matrix); + // If we are linked to a bone we must call setMatrixInSkeletonSpace + osgAnimation::Bone* b = dynamic_cast(node); + if (b) + { + osgAnimation::Bone* parent = b->getBoneParent(); + if (parent) + b->setMatrixInSkeletonSpace(matrix * parent->getMatrixInSkeletonSpace()); + else + b->setMatrixInSkeletonSpace(matrix); + } + traverse(node, nv); } From fbe84f4668c1fc3c51fe1e2139b71e99a67ead00 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 31 Mar 2024 23:08:47 +0300 Subject: [PATCH 345/451] Remove underwater fog radialization remnants The fog is now view-independent so this is not something to concern ourselves as with anymore --- files/shaders/compatibility/water.frag | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 2debf2fac0..268b9c99a2 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -152,23 +152,12 @@ void main(void) float ior = (cameraPos.z>0.0)?(1.333/1.0):(1.0/1.333); // air to water; water to air float fresnel = clamp(fresnel_dielectric(vVec, normal, ior), 0.0, 1.0); - float radialise = 1.0; - -#if @radialFog - float radialDepth = distance(position.xyz, cameraPos); - // TODO: Figure out how to properly radialise refraction depth and thus underwater fog - // while avoiding oddities when the water plane is close to the clipping plane - // radialise = radialDepth / linearDepth; -#else - float radialDepth = 0.0; -#endif - vec2 screenCoordsOffset = normal.xy * REFL_BUMP; #if REFRACTION - float depthSample = linearizeDepth(sampleRefractionDepthMap(screenCoords), near, far) * radialise; - float surfaceDepth = linearizeDepth(gl_FragCoord.z, near, far) * radialise; + float depthSample = linearizeDepth(sampleRefractionDepthMap(screenCoords), near, far); + float surfaceDepth = linearizeDepth(gl_FragCoord.z, near, far); float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum - float depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far) * radialise; + float depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far); float waterDepthDistorted = max(depthSampleDistorted - surfaceDepth, 0.0); screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); #endif @@ -196,7 +185,7 @@ void main(void) if (cameraPos.z > 0.0 && realWaterDepth <= VISIBILITY_DEPTH && waterDepthDistorted > VISIBILITY_DEPTH) screenCoordsOffset = vec2(0.0); - depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far) * radialise; + depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far); waterDepthDistorted = max(depthSampleDistorted - surfaceDepth, 0.0); // fade to realWaterDepth at a distance to compensate for physically inaccurate depth calculation @@ -248,6 +237,12 @@ void main(void) gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.a, 0.0, 1.0); #endif +#if @radialFog + float radialDepth = distance(position.xyz, cameraPos); +#else + float radialDepth = 0.0; +#endif + gl_FragData[0] = applyFogAtDist(gl_FragData[0], radialDepth, linearDepth, far); #if !@disableNormals From 0be7d7fa4c35197c5fad842aa49276d7e74000cc Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 1 Apr 2024 01:35:50 +0300 Subject: [PATCH 346/451] Reduce the amount of redundant code in the water shader --- files/shaders/compatibility/water.frag | 74 ++++++++++++-------------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 268b9c99a2..be2647a8df 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -10,8 +10,6 @@ #include "lib/core/fragment.h.glsl" -#define REFRACTION @refraction_enabled - // Inspired by Blender GLSL Water by martinsh ( https://devlog-martinsh.blogspot.de/2012/07/waterundewater-shader-wip.html ) // tweakables -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- @@ -20,18 +18,11 @@ const float VISIBILITY = 2500.0; const float VISIBILITY_DEPTH = VISIBILITY * 1.5; const float DEPTH_FADE = 0.15; -const float BIG_WAVES_X = 0.1; // strength of big waves -const float BIG_WAVES_Y = 0.1; - -const float MID_WAVES_X = 0.1; // strength of middle sized waves -const float MID_WAVES_Y = 0.1; -const float MID_WAVES_RAIN_X = 0.2; -const float MID_WAVES_RAIN_Y = 0.2; - -const float SMALL_WAVES_X = 0.1; // strength of small waves -const float SMALL_WAVES_Y = 0.1; -const float SMALL_WAVES_RAIN_X = 0.3; -const float SMALL_WAVES_RAIN_Y = 0.3; +const vec2 BIG_WAVES = vec2(0.1, 0.1); // strength of big waves +const vec2 MID_WAVES = vec2(0.1, 0.1); // strength of middle sized waves +const vec2 MID_WAVES_RAIN = vec2(0.2, 0.2); +const vec2 SMALL_WAVES = vec2(0.1, 0.1); // strength of small waves +const vec2 SMALL_WAVES_RAIN = vec2(0.3, 0.3); const float WAVE_CHOPPYNESS = 0.05; // wave choppyness const float WAVE_SCALE = 75.0; // overall wave scale @@ -133,9 +124,9 @@ void main(void) float distortionLevel = 2.0; rippleAdd += distortionLevel * vec3(texture2D(rippleMap, rippleMapUV).ba * blendFar * blendClose, 0.0); - vec2 bigWaves = vec2(BIG_WAVES_X,BIG_WAVES_Y); - vec2 midWaves = mix(vec2(MID_WAVES_X,MID_WAVES_Y),vec2(MID_WAVES_RAIN_X,MID_WAVES_RAIN_Y),rainIntensity); - vec2 smallWaves = mix(vec2(SMALL_WAVES_X,SMALL_WAVES_Y),vec2(SMALL_WAVES_RAIN_X,SMALL_WAVES_RAIN_Y),rainIntensity); + vec2 bigWaves = BIG_WAVES; + vec2 midWaves = mix(MID_WAVES, MID_WAVES_RAIN, rainIntensity); + vec2 smallWaves = mix(SMALL_WAVES, SMALL_WAVES_RAIN, rainIntensity); float bump = mix(BUMP,BUMP_RAIN,rainIntensity); vec3 normal = (normal0 * bigWaves.x + normal1 * bigWaves.y + normal2 * midWaves.x + @@ -153,34 +144,32 @@ void main(void) float fresnel = clamp(fresnel_dielectric(vVec, normal, ior), 0.0, 1.0); vec2 screenCoordsOffset = normal.xy * REFL_BUMP; -#if REFRACTION +#if @refraction_enabled float depthSample = linearizeDepth(sampleRefractionDepthMap(screenCoords), near, far); float surfaceDepth = linearizeDepth(gl_FragCoord.z, near, far); float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum float depthSampleDistorted = linearizeDepth(sampleRefractionDepthMap(screenCoords - screenCoordsOffset), near, far); float waterDepthDistorted = max(depthSampleDistorted - surfaceDepth, 0.0); - screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1); + screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH, 0.0, 1.0); #endif // reflection vec3 reflection = sampleReflectionMap(screenCoords + screenCoordsOffset).rgb; - // specular - float specular = pow(max(dot(reflect(vVec, normal), lVec), 0.0),SPEC_HARDNESS) * shadow; - vec3 waterColor = WATER_COLOR * sunFade; vec4 sunSpec = lcalcSpecular(0); // alpha component is sun visibility; we want to start fading lighting effects when visibility is low sunSpec.a = min(1.0, sunSpec.a / SUN_SPEC_FADING_THRESHOLD); + // specular + float specular = pow(max(dot(reflect(vVec, normal), lVec), 0.0), SPEC_HARDNESS) * shadow * sunSpec.a; + // artificial specularity to make rain ripples more noticeable vec3 skyColorEstimate = vec3(max(0.0, mix(-0.3, 1.0, sunFade))); vec3 rainSpecular = abs(rainRipple.w)*mix(skyColorEstimate, vec3(1.0), 0.05)*0.5; + float waterTransparency = clamp(fresnel * 6.0 + specular, 0.0, 1.0); -#if REFRACTION - // no alpha here, so make sure raindrop ripple specularity gets properly subdued - rainSpecular *= clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); - +#if @refraction_enabled // selectively nullify screenCoordsOffset to eliminate remaining shore artifacts, not needed for reflection if (cameraPos.z > 0.0 && realWaterDepth <= VISIBILITY_DEPTH && waterDepthDistorted > VISIBILITY_DEPTH) screenCoordsOffset = vec2(0.0); @@ -211,30 +200,35 @@ void main(void) normal3 * midWaves.y * 0.2 + normal4 * smallWaves.x * 0.1 + normal5 * smallWaves.y * 0.1 + rippleAdd); lNormal = normalize(vec3(-lNormal.x * bump, -lNormal.y * bump, lNormal.z)); float sunHeight = lVec.z; - vec3 scatterColour = mix(SCATTER_COLOUR*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0)); - vec3 lR = reflect(lVec, lNormal); - float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * sunSpec.a * clamp(1.0-exp(-sunHeight), 0.0, 1.0); + vec3 scatterColour = mix(SCATTER_COLOUR * vec3(1.0, 0.4, 0.0), SCATTER_COLOUR, max(1.0 - exp(-sunHeight * SUN_EXT), 0.0)); + float scatterLambert = max(dot(lVec, lNormal) * 0.7 + 0.3, 0.0); + float scatterReflectAngle = max(dot(reflect(lVec, lNormal), vVec) * 2.0 - 1.2, 0.0); + float lightScatter = scatterLambert * scatterReflectAngle * SCATTER_AMOUNT * sunFade * sunSpec.a * max(1.0 - exp(-sunHeight), 0.0); refraction = mix(refraction, scatterColour, lightScatter); #endif - gl_FragData[0].xyz = mix(refraction, reflection, fresnel) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; - gl_FragData[0].w = 1.0; + gl_FragData[0].rgb = mix(refraction, reflection, fresnel); + gl_FragData[0].a = 1.0; + // no alpha here, so make sure raindrop ripple specularity gets properly subdued + rainSpecular *= waterTransparency; +#else + gl_FragData[0].rgb = mix(waterColor, reflection, (1.0 + fresnel) * 0.5); + gl_FragData[0].a = waterTransparency; +#endif -#if @wobblyShores + gl_FragData[0].rgb += specular * sunSpec.rgb + rainSpecular; + +#if @refraction_enabled && @wobblyShores // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping vec3 normalShoreRippleRain = texture2D(normalMap,normalCoords(UV, 2.0, 2.7, -1.0*waterTimer, 0.05, 0.1, normal3)).rgb - 0.5 + texture2D(normalMap,normalCoords(UV, 2.0, 2.7, waterTimer, 0.04, -0.13, normal4)).rgb - 0.5; - float verticalWaterDepth = realWaterDepth * mix(abs(vVec.z), 1.0, 0.2); // an estimate + float viewFactor = mix(abs(vVec.z), 1.0, 0.2); + float verticalWaterDepth = realWaterDepth * viewFactor; // an estimate float shoreOffset = verticalWaterDepth - (normal2.r + mix(0.0, normalShoreRippleRain.r, rainIntensity) + 0.15)*8.0; - float fuzzFactor = min(1.0, 1000.0/surfaceDepth) * mix(abs(vVec.z), 1.0, 0.2); + float fuzzFactor = min(1.0, 1000.0 / surfaceDepth) * viewFactor; shoreOffset *= fuzzFactor; shoreOffset = clamp(mix(shoreOffset, 1.0, clamp(linearDepth / WOBBLY_SHORE_FADE_DISTANCE, 0.0, 1.0)), 0.0, 1.0); - gl_FragData[0].xyz = mix(rawRefraction, gl_FragData[0].xyz, shoreOffset); -#endif - -#else - gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; - gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.a, 0.0, 1.0); + gl_FragData[0].rgb = mix(rawRefraction, gl_FragData[0].rgb, shoreOffset); #endif #if @radialFog From 612177be09030804c64d3c01a593df40646cf41a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 2 Apr 2024 00:16:52 +0300 Subject: [PATCH 347/451] Improve some cryptic naming in the water shader --- files/shaders/compatibility/water.frag | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index be2647a8df..a18f22a797 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -133,15 +133,15 @@ void main(void) normal3 * midWaves.y + normal4 * smallWaves.x + normal5 * smallWaves.y + rippleAdd); normal = normalize(vec3(-normal.x * bump, -normal.y * bump, normal.z)); - vec3 lVec = normalize((gl_ModelViewMatrixInverse * vec4(lcalcPosition(0).xyz, 0.0)).xyz); + vec3 sunWorldDir = normalize((gl_ModelViewMatrixInverse * vec4(lcalcPosition(0).xyz, 0.0)).xyz); vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; - vec3 vVec = normalize(position.xyz - cameraPos.xyz); + vec3 viewDir = normalize(position.xyz - cameraPos.xyz); float sunFade = length(gl_LightModel.ambient.xyz); // fresnel float ior = (cameraPos.z>0.0)?(1.333/1.0):(1.0/1.333); // air to water; water to air - float fresnel = clamp(fresnel_dielectric(vVec, normal, ior), 0.0, 1.0); + float fresnel = clamp(fresnel_dielectric(viewDir, normal, ior), 0.0, 1.0); vec2 screenCoordsOffset = normal.xy * REFL_BUMP; #if @refraction_enabled @@ -162,7 +162,7 @@ void main(void) sunSpec.a = min(1.0, sunSpec.a / SUN_SPEC_FADING_THRESHOLD); // specular - float specular = pow(max(dot(reflect(vVec, normal), lVec), 0.0), SPEC_HARDNESS) * shadow * sunSpec.a; + float specular = pow(max(dot(reflect(viewDir, normal), sunWorldDir), 0.0), SPEC_HARDNESS) * shadow * sunSpec.a; // artificial specularity to make rain ripples more noticeable vec3 skyColorEstimate = vec3(max(0.0, mix(-0.3, 1.0, sunFade))); @@ -195,14 +195,13 @@ void main(void) } #if @sunlightScattering - // normal for sunlight scattering - vec3 lNormal = (normal0 * bigWaves.x * 0.5 + normal1 * bigWaves.y * 0.5 + normal2 * midWaves.x * 0.2 + - normal3 * midWaves.y * 0.2 + normal4 * smallWaves.x * 0.1 + normal5 * smallWaves.y * 0.1 + rippleAdd); - lNormal = normalize(vec3(-lNormal.x * bump, -lNormal.y * bump, lNormal.z)); - float sunHeight = lVec.z; + vec3 scatterNormal = (normal0 * bigWaves.x * 0.5 + normal1 * bigWaves.y * 0.5 + normal2 * midWaves.x * 0.2 + + normal3 * midWaves.y * 0.2 + normal4 * smallWaves.x * 0.1 + normal5 * smallWaves.y * 0.1 + rippleAdd); + scatterNormal = normalize(vec3(-scatterNormal.xy * bump, scatterNormal.z)); + float sunHeight = sunWorldDir.z; vec3 scatterColour = mix(SCATTER_COLOUR * vec3(1.0, 0.4, 0.0), SCATTER_COLOUR, max(1.0 - exp(-sunHeight * SUN_EXT), 0.0)); - float scatterLambert = max(dot(lVec, lNormal) * 0.7 + 0.3, 0.0); - float scatterReflectAngle = max(dot(reflect(lVec, lNormal), vVec) * 2.0 - 1.2, 0.0); + float scatterLambert = max(dot(sunWorldDir, scatterNormal) * 0.7 + 0.3, 0.0); + float scatterReflectAngle = max(dot(reflect(sunWorldDir, scatterNormal), viewDir) * 2.0 - 1.2, 0.0); float lightScatter = scatterLambert * scatterReflectAngle * SCATTER_AMOUNT * sunFade * sunSpec.a * max(1.0 - exp(-sunHeight), 0.0); refraction = mix(refraction, scatterColour, lightScatter); #endif @@ -222,7 +221,7 @@ void main(void) // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping vec3 normalShoreRippleRain = texture2D(normalMap,normalCoords(UV, 2.0, 2.7, -1.0*waterTimer, 0.05, 0.1, normal3)).rgb - 0.5 + texture2D(normalMap,normalCoords(UV, 2.0, 2.7, waterTimer, 0.04, -0.13, normal4)).rgb - 0.5; - float viewFactor = mix(abs(vVec.z), 1.0, 0.2); + float viewFactor = mix(abs(viewDir.z), 1.0, 0.2); float verticalWaterDepth = realWaterDepth * viewFactor; // an estimate float shoreOffset = verticalWaterDepth - (normal2.r + mix(0.0, normalShoreRippleRain.r, rainIntensity) + 0.15)*8.0; float fuzzFactor = min(1.0, 1000.0 / surfaceDepth) * viewFactor; From f2e0129436248676af20f82a61f7cea94f7a7d29 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 4 Apr 2024 21:12:47 +0300 Subject: [PATCH 348/451] Convert water/ripple defines to camelCase --- apps/opencs/model/world/data.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwrender/ripples.cpp | 2 +- apps/openmw/mwrender/water.cpp | 8 ++++---- files/shaders/compatibility/ripples_blobber.frag | 2 +- files/shaders/compatibility/ripples_simulate.frag | 4 ++-- files/shaders/compatibility/water.frag | 6 +++--- files/shaders/compatibility/water.vert | 2 +- files/shaders/lib/core/fragment.glsl | 2 +- files/shaders/lib/core/fragment.h.glsl | 2 +- files/shaders/lib/core/fragment_multiview.glsl | 2 +- files/shaders/lib/water/rain_ripples.glsl | 10 ++++------ 12 files changed, 21 insertions(+), 23 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 0a04954091..470ce04131 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -161,7 +161,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data defines["radialFog"] = "0"; defines["lightingModel"] = "0"; defines["reverseZ"] = "0"; - defines["refraction_enabled"] = "0"; + defines["waterRefraction"] = "0"; for (const auto& define : shadowDefines) defines[define.first] = define.second; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index acc8976219..dc71e455b2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -441,7 +441,7 @@ namespace MWRender globalDefines["radialFog"] = (exponentialFog || Settings::fog().mRadialFog) ? "1" : "0"; globalDefines["exponentialFog"] = exponentialFog ? "1" : "0"; globalDefines["skyBlending"] = mSkyBlending ? "1" : "0"; - globalDefines["refraction_enabled"] = "0"; + globalDefines["waterRefraction"] = "0"; globalDefines["useGPUShader4"] = "0"; globalDefines["useOVR_multiview"] = "0"; globalDefines["numViews"] = "1"; diff --git a/apps/openmw/mwrender/ripples.cpp b/apps/openmw/mwrender/ripples.cpp index 28427c3671..d0069d8d92 100644 --- a/apps/openmw/mwrender/ripples.cpp +++ b/apps/openmw/mwrender/ripples.cpp @@ -100,7 +100,7 @@ namespace MWRender { auto& shaderManager = mResourceSystem->getSceneManager()->getShaderManager(); - Shader::ShaderManager::DefineMap defineMap = { { "ripple_map_size", std::to_string(sRTTSize) + ".0" } }; + Shader::ShaderManager::DefineMap defineMap = { { "rippleMapSize", std::to_string(sRTTSize) + ".0" } }; osg::ref_ptr vertex = shaderManager.getShader("fullscreen_tri.vert", {}, osg::Shader::VERTEX); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 62266d6e2d..a28ca0b7b7 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -700,11 +700,11 @@ namespace MWRender { // use a define map to conditionally compile the shader std::map defineMap; - defineMap["refraction_enabled"] = std::string(mRefraction ? "1" : "0"); + defineMap["waterRefraction"] = std::string(mRefraction ? "1" : "0"); const int rippleDetail = Settings::water().mRainRippleDetail; - defineMap["rain_ripple_detail"] = std::to_string(rippleDetail); - defineMap["ripple_map_world_scale"] = std::to_string(RipplesSurface::sWorldScaleFactor); - defineMap["ripple_map_size"] = std::to_string(RipplesSurface::sRTTSize) + ".0"; + defineMap["rainRippleDetail"] = std::to_string(rippleDetail); + defineMap["rippleMapWorldScale"] = std::to_string(RipplesSurface::sWorldScaleFactor); + defineMap["rippleMapSize"] = std::to_string(RipplesSurface::sRTTSize) + ".0"; defineMap["sunlightScattering"] = Settings::water().mSunlightScattering ? "1" : "0"; defineMap["wobblyShores"] = Settings::water().mWobblyShores ? "1" : "0"; diff --git a/files/shaders/compatibility/ripples_blobber.frag b/files/shaders/compatibility/ripples_blobber.frag index ea874af83e..d9cadcda98 100644 --- a/files/shaders/compatibility/ripples_blobber.frag +++ b/files/shaders/compatibility/ripples_blobber.frag @@ -13,7 +13,7 @@ uniform vec2 offset; void main() { - vec2 uv = (gl_FragCoord.xy + offset) / @ripple_map_size; + vec2 uv = (gl_FragCoord.xy + offset) / @rippleMapSize; vec4 color = texture2D(imageIn, uv); float wavesizeMultiplier = getTemporalWaveSizeMultiplier(osg_SimulationTime); diff --git a/files/shaders/compatibility/ripples_simulate.frag b/files/shaders/compatibility/ripples_simulate.frag index f36cab5b40..fb416df2c6 100644 --- a/files/shaders/compatibility/ripples_simulate.frag +++ b/files/shaders/compatibility/ripples_simulate.frag @@ -6,9 +6,9 @@ uniform sampler2D imageIn; void main() { - vec2 uv = gl_FragCoord.xy / @ripple_map_size; + vec2 uv = gl_FragCoord.xy / @rippleMapSize; - float pixelSize = 1.0 / @ripple_map_size; + float pixelSize = 1.0 / @rippleMapSize; float oneOffset = pixelSize; float oneAndHalfOffset = 1.5 * pixelSize; diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index a18f22a797..d1324e01bd 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -144,7 +144,7 @@ void main(void) float fresnel = clamp(fresnel_dielectric(viewDir, normal, ior), 0.0, 1.0); vec2 screenCoordsOffset = normal.xy * REFL_BUMP; -#if @refraction_enabled +#if @waterRefraction float depthSample = linearizeDepth(sampleRefractionDepthMap(screenCoords), near, far); float surfaceDepth = linearizeDepth(gl_FragCoord.z, near, far); float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum @@ -169,7 +169,7 @@ void main(void) vec3 rainSpecular = abs(rainRipple.w)*mix(skyColorEstimate, vec3(1.0), 0.05)*0.5; float waterTransparency = clamp(fresnel * 6.0 + specular, 0.0, 1.0); -#if @refraction_enabled +#if @waterRefraction // selectively nullify screenCoordsOffset to eliminate remaining shore artifacts, not needed for reflection if (cameraPos.z > 0.0 && realWaterDepth <= VISIBILITY_DEPTH && waterDepthDistorted > VISIBILITY_DEPTH) screenCoordsOffset = vec2(0.0); @@ -217,7 +217,7 @@ void main(void) gl_FragData[0].rgb += specular * sunSpec.rgb + rainSpecular; -#if @refraction_enabled && @wobblyShores +#if @waterRefraction && @wobblyShores // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping vec3 normalShoreRippleRain = texture2D(normalMap,normalCoords(UV, 2.0, 2.7, -1.0*waterTimer, 0.05, 0.1, normal3)).rgb - 0.5 + texture2D(normalMap,normalCoords(UV, 2.0, 2.7, waterTimer, 0.04, -0.13, normal4)).rgb - 0.5; diff --git a/files/shaders/compatibility/water.vert b/files/shaders/compatibility/water.vert index 93796a7b9c..a67f412a07 100644 --- a/files/shaders/compatibility/water.vert +++ b/files/shaders/compatibility/water.vert @@ -21,7 +21,7 @@ void main(void) position = gl_Vertex; worldPos = position.xyz + nodePosition.xyz; - rippleMapUV = (worldPos.xy - playerPos.xy + (@ripple_map_size * @ripple_map_world_scale / 2.0)) / @ripple_map_size / @ripple_map_world_scale; + rippleMapUV = (worldPos.xy - playerPos.xy + (@rippleMapSize * @rippleMapWorldScale / 2.0)) / @rippleMapSize / @rippleMapWorldScale; vec4 viewPos = modelToView(gl_Vertex); linearDepth = getLinearDepth(gl_Position.z, viewPos.z); diff --git a/files/shaders/lib/core/fragment.glsl b/files/shaders/lib/core/fragment.glsl index 68fbe5e39d..9b983148cb 100644 --- a/files/shaders/lib/core/fragment.glsl +++ b/files/shaders/lib/core/fragment.glsl @@ -9,7 +9,7 @@ vec4 sampleReflectionMap(vec2 uv) return texture2D(reflectionMap, uv); } -#if @refraction_enabled +#if @waterRefraction uniform sampler2D refractionMap; uniform sampler2D refractionDepthMap; diff --git a/files/shaders/lib/core/fragment.h.glsl b/files/shaders/lib/core/fragment.h.glsl index 1bc2a1f79a..b8c3f9a32b 100644 --- a/files/shaders/lib/core/fragment.h.glsl +++ b/files/shaders/lib/core/fragment.h.glsl @@ -6,7 +6,7 @@ vec4 sampleReflectionMap(vec2 uv); -#if @refraction_enabled +#if @waterRefraction vec4 sampleRefractionMap(vec2 uv); float sampleRefractionDepthMap(vec2 uv); #endif diff --git a/files/shaders/lib/core/fragment_multiview.glsl b/files/shaders/lib/core/fragment_multiview.glsl index cc804d6c80..2880087104 100644 --- a/files/shaders/lib/core/fragment_multiview.glsl +++ b/files/shaders/lib/core/fragment_multiview.glsl @@ -12,7 +12,7 @@ vec4 sampleReflectionMap(vec2 uv) return texture(reflectionMap, vec3((uv), gl_ViewID_OVR)); } -#if @refraction_enabled +#if @waterRefraction uniform sampler2DArray refractionMap; uniform sampler2DArray refractionDepthMap; diff --git a/files/shaders/lib/water/rain_ripples.glsl b/files/shaders/lib/water/rain_ripples.glsl index 4e5f85017b..6ec3f101fe 100644 --- a/files/shaders/lib/water/rain_ripples.glsl +++ b/files/shaders/lib/water/rain_ripples.glsl @@ -1,8 +1,6 @@ #ifndef LIB_WATER_RIPPLES #define LIB_WATER_RIPPLES -#define RAIN_RIPPLE_DETAIL @rain_ripple_detail - const float RAIN_RIPPLE_GAPS = 10.0; const float RAIN_RIPPLE_RADIUS = 0.2; @@ -51,7 +49,7 @@ vec4 circle(vec2 coords, vec2 corner, float adjusted_time) float d = length(toCenter); float ringfollower = (phase-d/r)/RAIN_RING_TIME_OFFSET-1.0; // -1.0 ~ +1.0 cover the breadth of the ripple's ring -#if RAIN_RIPPLE_DETAIL > 0 +#if @rainRippleDetail > 0 // normal mapped ripples if(ringfollower < -1.0 || ringfollower > 1.0) return vec4(0.0); @@ -88,7 +86,7 @@ vec4 rain(vec2 uv, float time) vec2 f_part = fract(uv); vec2 i_part = floor(uv); float adjusted_time = time * 1.2 + randPhase(i_part); -#if RAIN_RIPPLE_DETAIL > 0 +#if @rainRippleDetail > 0 vec4 a = circle(f_part, i_part, adjusted_time); vec4 b = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET); vec4 c = circle(f_part, i_part, adjusted_time - RAIN_RING_TIME_OFFSET*2.0); @@ -115,11 +113,11 @@ vec4 rainCombined(vec2 uv, float time) // returns ripple normal in xyz and fake return rain(uv, time) + rain(complex_mult(uv, vec2(0.4, 0.7)) + vec2(1.2, 3.0),time) - #if RAIN_RIPPLE_DETAIL == 2 +#if @rainRippleDetail == 2 + rain(uv * 0.75 + vec2( 3.7,18.9),time) + rain(uv * 0.9 + vec2( 5.7,30.1),time) + rain(uv * 1.0 + vec2(10.5 ,5.7),time) - #endif +#endif ; } From a51d560174d90ac55049fd8ab687533dfe24fbfc Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 5 Apr 2024 01:59:40 +0100 Subject: [PATCH 349/451] Move bone rename logic to ColladaDescriptionVisitor, undo formatting/refactoring --- apps/openmw/mwrender/animation.cpp | 5 ++++- apps/openmw/mwrender/rotatecontroller.cpp | 1 + components/misc/strings/algorithm.hpp | 2 +- components/resource/keyframemanager.cpp | 4 +++- components/resource/scenemanager.cpp | 26 ++++++++++------------- components/sceneutil/extradata.cpp | 6 +----- components/sceneutil/osgacontroller.cpp | 2 +- components/sceneutil/osgacontroller.hpp | 2 +- 8 files changed, 23 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 1807b0050f..729c69f58b 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1457,9 +1457,10 @@ namespace MWRender } } - osg::ref_ptr created = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); if (!forceskeleton) { + osg::ref_ptr created + = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); mInsert->addChild(created); mObjectRoot = created->asGroup(); if (!mObjectRoot) @@ -1475,6 +1476,8 @@ namespace MWRender } else { + osg::ref_ptr created + = getModelInstance(mResourceSystem, model, baseonly, inject, defaultSkeleton); osg::ref_ptr skel = dynamic_cast(created.get()); if (!skel) { diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index 73c4cddff5..47b271c40d 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -41,6 +41,7 @@ namespace MWRender osg::Quat orient = worldOrient * mRotate * worldOrientInverse * matrix.getRotate(); matrix.setRotate(orient); matrix.setTrans(matrix.getTrans() + worldOrientInverse * mOffset); + node->setMatrix(matrix); // If we are linked to a bone we must call setMatrixInSkeletonSpace diff --git a/components/misc/strings/algorithm.hpp b/components/misc/strings/algorithm.hpp index 2bf7125c8b..18f72104bd 100644 --- a/components/misc/strings/algorithm.hpp +++ b/components/misc/strings/algorithm.hpp @@ -15,7 +15,7 @@ namespace Misc::StringUtils bool operator()(char x, char y) const { return toLower(x) < toLower(y); } }; - inline std::string underscoresToSpaces(const std::string_view& oldName) + inline std::string underscoresToSpaces(const std::string_view oldName) { std::string newName(oldName); std::replace(newName.begin(), newName.end(), '_', ' '); diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 67a434e47b..84e7a7e311 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -88,7 +88,9 @@ namespace Resource { //"Default" is osg dae plugin's default naming scheme for unnamed animations if (animation->getName() == "Default") + { animation->setName(std::string("idle")); + } osg::ref_ptr mergedAnimationTrack = new Resource::Animation; const std::string animationName = animation->getName(); @@ -97,7 +99,7 @@ namespace Resource const osgAnimation::ChannelList& channels = animation->getChannels(); for (const auto& channel : channels) { - // Repalce channel target name to match the renamed bones/transforms + // Replace channel target name to match the renamed bones/transforms channel->setTargetName(Misc::StringUtils::underscoresToSpaces(channel->getTargetName())); if (name == "Bip01 R Clavicle") diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index b8272c8b26..3b7deeaab3 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -271,6 +271,11 @@ namespace Resource void apply(osg::Node& node) override { + // If an osgAnimation bone/transform, ensure underscores in name are replaced with spaces + // this is for compatibility reasons + if (node.libraryName() == std::string_view("osgAnimation") && node.className() == std::string_view("Bone")) + node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); + if (osg::StateSet* stateset = node.getStateSet()) { if (stateset->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) @@ -362,27 +367,18 @@ namespace Resource if (!vertexInfluenceMap) return; - std::vector> renameList; - - // Collecting updates - for (const auto& influence : *vertexInfluenceMap) + std::vector renameList; + for (const auto& [boneName, unused] : *vertexInfluenceMap) { - const std::string& oldBoneName = influence.first; - std::string newBoneName = Misc::StringUtils::underscoresToSpaces(oldBoneName); - if (newBoneName != oldBoneName) - renameList.emplace_back(oldBoneName, newBoneName); + if (boneName.find('_') != std::string::npos) + renameList.push_back(boneName); } - // Applying updates (cant update map while iterating it!) - for (const auto& rename : renameList) + for (const std::string& oldName : renameList) { - const std::string& oldName = rename.first; - const std::string& newName = rename.second; - - // Check if new name already exists to avoid overwriting + const std::string newName = Misc::StringUtils::underscoresToSpaces(oldName); if (vertexInfluenceMap->find(newName) == vertexInfluenceMap->end()) (*vertexInfluenceMap)[newName] = std::move((*vertexInfluenceMap)[oldName]); - vertexInfluenceMap->erase(oldName); } } diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index 8d024d5824..5e91830bba 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -45,15 +45,11 @@ namespace SceneUtil void ProcessExtraDataVisitor::apply(osg::Node& node) { - // If an osgAnimation bone/transform, ensure underscores in name are replaced with spaces - // this is for compatibility reasons - if (node.libraryName() == std::string_view("osgAnimation") && node.className() == std::string_view("Bone")) - node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); - if (!mSceneMgr->getSoftParticles()) return; 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()) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 40b6640973..5a3ae60293 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -134,7 +134,7 @@ namespace SceneUtil return osg::Vec3f(); } - osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string& name) const + osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string_view name) const { std::string animationName; float newTime = time; diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp index 000580631c..bb9a621760 100644 --- a/components/sceneutil/osgacontroller.hpp +++ b/components/sceneutil/osgacontroller.hpp @@ -60,7 +60,7 @@ namespace SceneUtil osg::Vec3f getTranslation(float time) const override; /// @brief Handles finding bone position in the animation - osg::Matrixf getTransformForNode(float time, const std::string& name) const; + osg::Matrixf getTransformForNode(float time, const std::string_view name) const; /// @brief Calls animation track update() void update(float time, const std::string& animationName); From 8fecbb55ffca2d4edbd091b41ecb2aa3bdacfa3c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 5 Apr 2024 10:12:31 +0400 Subject: [PATCH 350/451] Do not store 'location' tags in translation files --- CI/check_qt_translations.sh | 6 +- CMakeLists.txt | 6 +- files/lang/components_ru.ts | 2 +- files/lang/launcher_ru.ts | 6 +- files/lang/wizard_de.ts | 182 ----------------------------------- files/lang/wizard_fr.ts | 182 ----------------------------------- files/lang/wizard_ru.ts | 186 +----------------------------------- 7 files changed, 12 insertions(+), 558 deletions(-) diff --git a/CI/check_qt_translations.sh b/CI/check_qt_translations.sh index f3a82ed2e6..1fc2e19002 100755 --- a/CI/check_qt_translations.sh +++ b/CI/check_qt_translations.sh @@ -4,8 +4,8 @@ set -o pipefail LUPDATE="${LUPDATE:-lupdate}" -${LUPDATE:?} apps/wizard -ts files/lang/wizard_*.ts -${LUPDATE:?} apps/launcher -ts files/lang/launcher_*.ts -${LUPDATE:?} components/contentselector components/process -ts files/lang/components_*.ts +${LUPDATE:?} -locations none apps/wizard -ts files/lang/wizard_*.ts +${LUPDATE:?} -locations none apps/launcher -ts files/lang/launcher_*.ts +${LUPDATE:?} -locations none components/contentselector components/process -ts files/lang/components_*.ts ! (git diff --name-only | grep -q "^") || (echo -e "\033[0;31mBuild a 'translations' CMake target to update Qt localization for these files:\033[0;0m"; git diff --name-only | xargs -i echo -e "\033[0;31m{}\033[0;0m"; exit -1) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bffdba34e..93da5feec4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1106,17 +1106,17 @@ if (USE_QT) file(GLOB COMPONENTS_TS_FILES ${CMAKE_SOURCE_DIR}/files/lang/components_*.ts) get_target_property(QT_LUPDATE_EXECUTABLE Qt::lupdate IMPORTED_LOCATION) add_custom_target(translations - COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/components/contentselector ${CMAKE_SOURCE_DIR}/components/process -ts ${COMPONENTS_TS_FILES} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/components/contentselector ${CMAKE_SOURCE_DIR}/components/process -ts ${COMPONENTS_TS_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/components VERBATIM COMMAND_EXPAND_LISTS - COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/apps/wizard -ts ${WIZARD_TS_FILES} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/apps/wizard -ts ${WIZARD_TS_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/wizard VERBATIM COMMAND_EXPAND_LISTS - COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/apps/launcher -ts ${LAUNCHER_TS_FILES} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/apps/launcher -ts ${LAUNCHER_TS_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/launcher VERBATIM COMMAND_EXPAND_LISTS) diff --git a/files/lang/components_ru.ts b/files/lang/components_ru.ts index cca6591afe..3fe4db1e6f 100644 --- a/files/lang/components_ru.ts +++ b/files/lang/components_ru.ts @@ -65,7 +65,7 @@ Arguments: Параметры: - + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index ec7aeccc57..52499b7b3c 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -109,7 +109,7 @@ &New Content List - Новый список плагинов + &Новый список плагинов Clone Content List @@ -527,7 +527,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <br><b>Could not create directory %0</b><br><br>%1<br> - <br><b>Не удалось создать директорию %0</b><br><br> + <br><b>Не удалось создать директорию %0</b><br><br>%1<br> @@ -1046,7 +1046,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Size of characters in game texts. - Размер символов в текстах + Размер символов в текстах. Font size diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts index 7bf54e90b1..5749cf2d5d 100644 --- a/files/lang/wizard_de.ts +++ b/files/lang/wizard_de.ts @@ -4,27 +4,22 @@ ComponentSelectionPage - WizardPage - Select Components - Which components should be installed? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> - Selected components: @@ -32,17 +27,14 @@ ConclusionPage - WizardPage - Completing the OpenMW Wizard - Placeholder @@ -50,27 +42,22 @@ ExistingInstallationPage - WizardPage - Select Existing Installation - Select an existing installation for OpenMW to use or modify. - Detected installations: - Browse... @@ -78,42 +65,34 @@ ImportPage - WizardPage - Import Settings - Import settings from the Morrowind installation. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> - Import settings from Morrowind.ini - Import add-on and plugin selection - Import bitmap fonts setup from Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. @@ -123,17 +102,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationPage - WizardPage - Installing - Please wait while Morrowind is installed on your computer. @@ -141,32 +117,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationTargetPage - WizardPage - Select Installation Destination - Where should Morrowind be installed? - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. - Browse... @@ -174,17 +144,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov IntroPage - WizardPage - Welcome to the OpenMW Wizard - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. @@ -192,27 +159,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov LanguageSelectionPage - WizardPage - Select Morrowind Language - What is the language of the Morrowind installation? - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. @@ -220,62 +182,50 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov MethodSelectionPage - WizardPage - Select Installation Method - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. - Existing Installation - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. - Don't have a copy? - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game @@ -283,42 +233,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> - B&rowse... - Select configuration file - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> - Most recent Morrowind not detected - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? @@ -326,57 +268,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ComponentSelectionPage - &Install - &Skip - Morrowind (installed) - Morrowind - Tribunal (installed) - Tribunal - Bloodmoon (installed) - Bloodmoon - About to install Tribunal after Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> - Re-install &Bloodmoon @@ -384,17 +315,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> @@ -402,32 +330,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ExistingInstallationPage - No existing installations detected - Error detecting Morrowind configuration - Morrowind configuration file (*.ini) - Select Morrowind.esm (located in Data Files) - Morrowind master file (Morrowind.esm) - Error detecting Morrowind files @@ -435,78 +357,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationPage - <p>Attempting to install component %1.</p> - Attempting to install component %1. - %1 Installation - Select %1 installation media - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> - <p>Detected old version of component Morrowind.</p> - Detected old version of component Morrowind. - Morrowind Installation - Installation finished - Installation completed successfully! - Installation failed! - <p><br/><span style="color:red;"><b>Error: %1</b></p> - <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> - An error occurred @@ -514,37 +420,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationTargetPage - Error creating destination - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> - Insufficient permissions - Destination not empty - Select where to install Morrowind @@ -552,37 +451,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::LanguageSelectionPage - English - French - German - Italian - Polish - Russian - Spanish @@ -590,59 +482,42 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - OpenMW Wizard - - Error opening Wizard log file - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - - Error opening OpenMW configuration file - Quit Wizard - Are you sure you want to exit the Wizard? - Error creating OpenMW configuration directory - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - Error writing OpenMW configuration file @@ -650,207 +525,150 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! - - Opening %1 failed: %2. - Failed to write Morrowind configuration file! - Writing to %1 failed: %2. - Installing: %1 - - Installing: %1 directory - Installation finished! - - Component parameter is invalid! - - An invalid component parameter was supplied. - Failed to find a valid archive containing %1.bsa! Retrying. - Installing %1 - Installation media path not set! - The source path for %1 was not set. - - Cannot create temporary directory! - - Failed to create %1. - Cannot move into temporary directory! - Failed to move into %1. - Moving installation files - - - - Could not install directory! - - - - Installing %1 to %2 failed. - Could not install translation file! - Failed to install *%1 files. - Could not install Morrowind data file! - - Failed to install %1. - Could not install Morrowind configuration file! - Installing: Sound directory - Could not find Tribunal data file! - - - Failed to find %1. - Could not find Tribunal patch file! - Could not find Bloodmoon data file! - Updating Morrowind configuration file - %1 installation finished! - Extracting: %1 - - - Failed to open InstallShield Cabinet File. - - - Opening %1 failed. - Failed to extract %1. - Complete path: %1 diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index 7f42087dbf..9b5acbc9e9 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -4,27 +4,22 @@ ComponentSelectionPage - WizardPage - Select Components - Which components should be installed? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> - Selected components: @@ -32,17 +27,14 @@ ConclusionPage - WizardPage - Completing the OpenMW Wizard - Placeholder @@ -50,27 +42,22 @@ ExistingInstallationPage - WizardPage - Select Existing Installation - Select an existing installation for OpenMW to use or modify. - Detected installations: - Browse... @@ -78,42 +65,34 @@ ImportPage - WizardPage - Import Settings - Import settings from the Morrowind installation. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> - Import settings from Morrowind.ini - Import add-on and plugin selection - Import bitmap fonts setup from Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. @@ -123,17 +102,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationPage - WizardPage - Installing - Please wait while Morrowind is installed on your computer. @@ -141,32 +117,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationTargetPage - WizardPage - Select Installation Destination - Where should Morrowind be installed? - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. - Browse... @@ -174,17 +144,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov IntroPage - WizardPage - Welcome to the OpenMW Wizard - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. @@ -192,27 +159,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov LanguageSelectionPage - WizardPage - Select Morrowind Language - What is the language of the Morrowind installation? - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. @@ -220,62 +182,50 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov MethodSelectionPage - WizardPage - Select Installation Method - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. - Existing Installation - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. - Don't have a copy? - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game @@ -283,42 +233,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> - B&rowse... - Select configuration file - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> - Most recent Morrowind not detected - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? @@ -326,57 +268,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ComponentSelectionPage - &Install - &Skip - Morrowind (installed) - Morrowind - Tribunal (installed) - Tribunal - Bloodmoon (installed) - Bloodmoon - About to install Tribunal after Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> - Re-install &Bloodmoon @@ -384,17 +315,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> @@ -402,32 +330,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ExistingInstallationPage - No existing installations detected - Error detecting Morrowind configuration - Morrowind configuration file (*.ini) - Select Morrowind.esm (located in Data Files) - Morrowind master file (Morrowind.esm) - Error detecting Morrowind files @@ -435,78 +357,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationPage - <p>Attempting to install component %1.</p> - Attempting to install component %1. - %1 Installation - Select %1 installation media - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> - <p>Detected old version of component Morrowind.</p> - Detected old version of component Morrowind. - Morrowind Installation - Installation finished - Installation completed successfully! - Installation failed! - <p><br/><span style="color:red;"><b>Error: %1</b></p> - <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> - An error occurred @@ -514,37 +420,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationTargetPage - Error creating destination - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> - Insufficient permissions - Destination not empty - Select where to install Morrowind @@ -552,37 +451,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::LanguageSelectionPage - English - French - German - Italian - Polish - Russian - Spanish @@ -590,59 +482,42 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - OpenMW Wizard - - Error opening Wizard log file - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - - Error opening OpenMW configuration file - Quit Wizard - Are you sure you want to exit the Wizard? - Error creating OpenMW configuration directory - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - Error writing OpenMW configuration file @@ -650,207 +525,150 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! - - Opening %1 failed: %2. - Failed to write Morrowind configuration file! - Writing to %1 failed: %2. - Installing: %1 - - Installing: %1 directory - Installation finished! - - Component parameter is invalid! - - An invalid component parameter was supplied. - Failed to find a valid archive containing %1.bsa! Retrying. - Installing %1 - Installation media path not set! - The source path for %1 was not set. - - Cannot create temporary directory! - - Failed to create %1. - Cannot move into temporary directory! - Failed to move into %1. - Moving installation files - - - - Could not install directory! - - - - Installing %1 to %2 failed. - Could not install translation file! - Failed to install *%1 files. - Could not install Morrowind data file! - - Failed to install %1. - Could not install Morrowind configuration file! - Installing: Sound directory - Could not find Tribunal data file! - - - Failed to find %1. - Could not find Tribunal patch file! - Could not find Bloodmoon data file! - Updating Morrowind configuration file - %1 installation finished! - Extracting: %1 - - - Failed to open InstallShield Cabinet File. - - - Opening %1 failed. - Failed to extract %1. - Complete path: %1 diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts index 3113774cd3..0a2e6e5561 100644 --- a/files/lang/wizard_ru.ts +++ b/files/lang/wizard_ru.ts @@ -4,27 +4,22 @@ ComponentSelectionPage - WizardPage WizardPage - Select Components Выбор компонентов - Which components should be installed? Какие компоненты должны быть установлены? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> <html><head/><body><p>Выберите, какие дополнения для Morrowind нужно установить. Для достижения наилучших результатов рекомендуется установить оба дополнения.</p><p><span style=" font-weight:bold;">Подсказка:</span> Можно установить дополнения позже, запустив этот Мастер установки заново.<br/></p></body></html> - Selected components: Выбранные компоненты: @@ -32,17 +27,14 @@ ConclusionPage - WizardPage WizardPage - Completing the OpenMW Wizard Завершение работы Мастера установки OpenMW - Placeholder Placeholder @@ -50,27 +42,22 @@ ExistingInstallationPage - WizardPage WizardPage - Select Existing Installation Выбрать установленную копию игры - Select an existing installation for OpenMW to use or modify. Выбрать установленную копию игры для использования или изменения через OpenMW. - Detected installations: Обнаруженные установленные копии: - Browse... Выбрать... @@ -78,42 +65,34 @@ ImportPage - WizardPage WizardPage - Import Settings Импортировать настройки - Import settings from the Morrowind installation. Импортировать настройки из установленной копии Morrowind. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> <html><head/><body><p>Чтобы OpenMW мог работать правильно, ему нужно импортировать настройки из файла с настройками Morrowind.</p><p><span style=" font-weight:bold;">Подсказка:</span> Также можно импортировать настройки позже, запустив Мастер импорта заново.</p><p/></body></html> - Import settings from Morrowind.ini Импортировать настройки из Morrowind.ini - Import add-on and plugin selection Импортировать список подключенных плагинов - Import bitmap fonts setup from Morrowind.ini Импортировать растровые шрифты из Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. @@ -125,17 +104,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationPage - WizardPage WizardPage - Installing Установка - Please wait while Morrowind is installed on your computer. Пожалуйста, подождите, пока Morrowind устанавливается на ваш компьютер. @@ -143,32 +119,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationTargetPage - WizardPage WizardPage - Select Installation Destination Выберите путь для установки - Where should Morrowind be installed? Куда нужно установить Morrowind? - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. - Morrowind будет установлен в следующее место. + Morrowind будет установлен в следующее место. - Browse... Выбрать... @@ -176,17 +146,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov IntroPage - WizardPage WizardPage - Welcome to the OpenMW Wizard Добро пожаловать в Мастер установки - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. Этот Мастер поможет вам установить Morrowind и его дополнения, чтобы OpenMW мог их использовать. @@ -194,27 +161,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov LanguageSelectionPage - WizardPage WizardPage - Select Morrowind Language Выберите язык вашей копии Morrowind - What is the language of the Morrowind installation? Какой язык использует ваша копия Morrowind? - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> ><html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. Выберите язык, используемый вашей копией Morrowind. @@ -222,62 +184,50 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov MethodSelectionPage - WizardPage WizardPage - Select Installation Method Выберите способ установки - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> <html><head/><body><p>Выберите способ установки <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD CD/DVD-диск - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. - Установить игру с диска + Установить игру с диска. - Existing Installation Установленная копия игры - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. Выбрать установленную копию игры. - Don't have a copy? Нет копии игры? - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game Купить игру @@ -285,42 +235,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> <br><b>Не удалось найти Morrowind.ini</b><br><br>Мастеру требуется обновить настройки в этом файле.<br><br>Нажмите "Выбрать...", чтобы задать местоположение файла вручную.<br> - B&rowse... &Выбрать... - Select configuration file Выберите файл с настройками - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. <b>Morrowind.bsa</b> не найден!<br>Убедитесь, что Morrowind был установлен правильно. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> <br><b>Может существовать более свежая версия Morrowind.</b><br><br>Все равно продолжить?<br> - Most recent Morrowind not detected Актуальная версия Morrowind не найдена - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. Выберите корректный установочный дистрибутив %1.<br><b>Подсказка</b>: он должен содержать как минимум один <b>.cab</b>-файл. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? Может существовать более свежая версия Morrowind.<br><br>Все равно продолжить? @@ -328,57 +270,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ComponentSelectionPage - &Install &Установить - &Skip &Пропустить - Morrowind (installed) Morrowind (установлен) - Morrowind Morrowind - Tribunal (installed) Tribunal (установлен) - Tribunal Tribunal - Bloodmoon (installed) Bloodmoon (установлен) - Bloodmoon Bloodmoon - About to install Tribunal after Bloodmoon Попытка установить Tribunal после Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> <html><head/><body><p><b>Вы собираетесь установить Tribunal</b></p><p>Bloodmoon уже установлен на ваш компьютер.</p><p>Tribunal рекомендуется устанавлить перед установкой Bloodmoon.</p><p>Желаете ли вы переустановить Bloodmoon?</p></body></html> - Re-install &Bloodmoon Переустановить &Bloodmoon @@ -386,17 +317,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> <html><head/><body><p>Мастер OpenMW успешно установил Morrowind на ваш компьютер.</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> <html><head/><body><p>Мастер OpenMW успешно завершил изменение вашей установленной копии Morrowind.</body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> <html><head/><body><p>Мастеру OpenMW не удалось установить Morrowind на ваш компьютер.</p><p>Пожалуйста, сообщите о встреченных вами ошибках на наш <a href="https://gitlab.com/OpenMW/openmw/issues">багтрекер</a>.<br/>Не забудьте включить туда лог установки.</p><br/></body></html> @@ -404,32 +332,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ExistingInstallationPage - No existing installations detected Установленные копии игры не найдены - Error detecting Morrowind configuration Попытка найти настройки Morrowind завершилась ошибкой - Morrowind configuration file (*.ini) Файл настроек Morrowind (*.ini) - Select Morrowind.esm (located in Data Files) Выберите Morrowind.esm (расположен в Data Files) - Morrowind master file (Morrowind.esm) Мастер-файл Morrowind (Morrowind.esm) - Error detecting Morrowind files Не удалось обнаружить файлы Morrowind @@ -437,78 +359,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationPage - <p>Attempting to install component %1.</p> <p>Попытка установить компонент %1.</p> - Attempting to install component %1. Попытка установить компонент %1. - %1 Installation Установка %1 - Select %1 installation media Выберите установочный дистрибутив %1 - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> <p><br/><span style="color:red;"><b>Ошибка: Установка была прервана пользователем</b></span></p> - <p>Detected old version of component Morrowind.</p> lt;p>Обнаружена устаревшая версия компонента Morrowind.</p> - Detected old version of component Morrowind. Обнаружена устаревшая версия компонента Morrowind. - Morrowind Installation Установка Morrowind - Installation finished Установка завершена - Installation completed successfully! Установка успешно завершена! - Installation failed! Установка не удалась! - <p><br/><span style="color:red;"><b>Error: %1</b></p> <p><br/><span style="color:red;"><b>Ошибка: %1</b></p> - <p><span style="color:red;"><b>%1</b></p> <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> <html><head/><body><p><b>При работе Мастера возникла ошибка</b></p><p>Обнаруженная ошибка:</p><p>%1</p><p>Нажмите &quot;Показать детали...&quot; для получения дополнительной информации.</p></body></html> - An error occurred Произошла ошибка @@ -516,37 +422,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationTargetPage - Error creating destination Не удалось создать директорию назначения - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> <html><head/><body><p><b>Не удалось создать директорию назначения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку, или же выберите другую директорию.</p></body> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> <html><head/><body><p><b>Не удалось записать данные в директорию назначения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку, или же выберите другую директорию.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> <html><head/><body><p><b>Директория назначения содержит файлы</b></p><p>В указанной директории найдена установленная копия Morrowind.</p><p>Пожалуйста, выберите другую директорию, или же вернитесь на предыдущий шаг и выберите подключение установленной копии игры.</p></body></html> - Insufficient permissions Не хватает прав доступа - Destination not empty Выбранная директория не пустая - Select where to install Morrowind Выберите, куда установить Morrowind @@ -554,37 +453,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::LanguageSelectionPage - English Английский - French Французский - German Немецкий - Italian Итальянский - Polish Польский - Russian Русский - Spanish Испанский @@ -592,59 +484,42 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - OpenMW Wizard Мастер OpenMW - - Error opening Wizard log file Не удалось открыть лог-файл Мастера - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось открыть %1 для записи</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось открыть %1 для чтения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - - Error opening OpenMW configuration file Не удалось открыть файл с настройками OpenMW - Quit Wizard Завершить работу Мастера - Are you sure you want to exit the Wizard? Вы уверены, что хотите завершить работу Мастера? - Error creating OpenMW configuration directory Не удалось создать директорию для настроек OpenMW - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось создать %1</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - Error writing OpenMW configuration file Не удалось записать данные в файл с настройками OpenMW @@ -652,207 +527,150 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! Не удалось открыть файл с настройками Morrowind! - - Opening %1 failed: %2. Попытка открыть %1 не удалась: %2. - Failed to write Morrowind configuration file! Не удалось записать данные в файл с настройками Morrowind! - Writing to %1 failed: %2. Запись в %1 завершилась с ошибкой: %2. - Installing: %1 Установка: %1 - - Installing: %1 directory Установка: директория %1 - Installation finished! Установка завершена! - - Component parameter is invalid! Некорректный параметр для компонента! - - An invalid component parameter was supplied. Задан некорректный параметр для компонента. - Failed to find a valid archive containing %1.bsa! Retrying. Не удалось найти архив, содержащий %1.bsa! Повторная попытка. - Installing %1 Установка %1 - Installation media path not set! путь к установочному дистрибутиву не задан! - The source path for %1 was not set. Исходный пусть для %1 не задан. - - Cannot create temporary directory! Не удалось создать временную директорию! - - Failed to create %1. Не удалось создать %1. - Cannot move into temporary directory! Не удалось переместить во временную директорию! - Failed to move into %1. Не удалось переместить в %1. - Moving installation files Перемещение файлов установки - - - - Could not install directory! Не удалось установить директорию! - - - - Installing %1 to %2 failed. Не удалось установить %1 в %2. - Could not install translation file! Не удалось установить файл с переводом! - Failed to install *%1 files. Не удалось установить файлы *%1. - Could not install Morrowind data file! Не удалось установить файл с данными Morrowind! - - Failed to install %1. Не удалось установить %1. - Could not install Morrowind configuration file! Не удалось установить файл с настройками Morrowind! - Installing: Sound directory Установка: директория Sound - Could not find Tribunal data file! Не удалось найти файл с данными Tribunal! - - - Failed to find %1. Не удалось найти %1. - Could not find Tribunal patch file! Не удалось найти файл с патчем для Tribunal! - Could not find Bloodmoon data file! Не удалось найти файл с данными Bloodmoon! - Updating Morrowind configuration file Обновление файла с настройками Morrowind - %1 installation finished! Установка %1 завершена! - Extracting: %1 Извлечение: %1 - - - Failed to open InstallShield Cabinet File. Не удалось открыть файл InstallShield Cabinet. - - - Opening %1 failed. Не удалось открыть %1. - Failed to extract %1. Не удалось извлечь %1. - Complete path: %1 Полный путь: %1 From 36cccef6067d674384f5039f95efababf73664ba Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 5 Apr 2024 23:43:59 +0100 Subject: [PATCH 351/451] Fix formatting --- apps/openmw/mwrender/animation.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 729c69f58b..82081658a6 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1805,7 +1805,6 @@ namespace MWRender osg::ref_ptr controller(new RotateController(mObjectRoot.get())); node->addUpdateCallback(controller); mActiveControllers.emplace_back(node, controller); - return controller; } From b0008d22e6a049f13e6fe9889ac326792670632e Mon Sep 17 00:00:00 2001 From: trav5 Date: Sat, 6 Apr 2024 23:07:11 +0200 Subject: [PATCH 352/451] Attempt to fix openmw issue #7906 l10n name is taken not from the ActionInfo/TriggerInfo's 'key' param, but from the 'l10n' param when making the inputBinding setting renderer --- files/data/scripts/omw/input/settings.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/input/settings.lua b/files/data/scripts/omw/input/settings.lua index 5243a86844..83a862b2d2 100644 --- a/files/data/scripts/omw/input/settings.lua +++ b/files/data/scripts/omw/input/settings.lua @@ -102,7 +102,7 @@ I.Settings.registerRenderer('inputBinding', function(id, set, arg) local info = inputTypes[arg.type][arg.key] if not info then print(string.format('inputBinding: %s %s not found', arg.type, arg.key)) return end - local l10n = core.l10n(info.key) + local l10n = core.l10n(info.l10n) local name = { template = I.MWUI.templates.textNormal, From 5856bc8a0eb5158f97702026aa9f7cb6cfc3ecec Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 6 Apr 2024 18:13:51 -0500 Subject: [PATCH 353/451] Add setCrimeLevel --- apps/openmw/mwlua/types/player.cpp | 4 ++++ files/lua_api/openmw/types.lua | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index 130d3ded21..144d98c678 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -186,6 +186,10 @@ namespace MWLua const MWWorld::Class& cls = o.ptr().getClass(); return cls.getNpcStats(o.ptr()).getBounty(); }; + player["setCrimeLevel"] = [](const Object& o, int amount) { + const MWWorld::Class& cls = o.ptr().getClass(); + cls.getNpcStats(o.ptr()).setBounty(amount); + }; player["isCharGenFinished"] = [](const Object&) -> bool { return MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1; }; diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 149d9bd9fa..bf1dc6cb4b 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1123,6 +1123,12 @@ -- @param openmw.core#GameObject player -- @return #number +--- +-- Sets the bounty or crime level of the player +-- @function [parent=#Player] setCrimeLevel +-- @param openmw.core#GameObject player +-- @param #number crimeLevel The requested crime level + --- -- Whether the character generation for this player is finished. -- @function [parent=#Player] isCharGenFinished From 4ca13a94049b9c59ff832779c849c87df05e11f8 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 6 Apr 2024 18:17:51 -0500 Subject: [PATCH 354/451] Verify the player --- apps/openmw/mwlua/types/player.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index 144d98c678..e517bdf77d 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -187,6 +187,9 @@ namespace MWLua return cls.getNpcStats(o.ptr()).getBounty(); }; player["setCrimeLevel"] = [](const Object& o, int amount) { + verifyPlayer(o); + if (!dynamic_cast(&o)) + throw std::runtime_error("Only global scripts can change crime level"); const MWWorld::Class& cls = o.ptr().getClass(); cls.getNpcStats(o.ptr()).setBounty(amount); }; From b859fc10e2745da0fa574f2aa42bab97ea0c2bd7 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 6 Apr 2024 18:18:14 -0500 Subject: [PATCH 355/451] Add doc note --- files/lua_api/openmw/types.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index bf1dc6cb4b..90344cbae1 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1124,7 +1124,7 @@ -- @return #number --- --- Sets the bounty or crime level of the player +-- Sets the bounty or crime level of the player, may only be used in global scripts -- @function [parent=#Player] setCrimeLevel -- @param openmw.core#GameObject player -- @param #number crimeLevel The requested crime level From d68d20eb1ae4dfa0303a4b745d69b7bb04bbb05e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Apr 2024 10:42:29 +0200 Subject: [PATCH 356/451] Disallow Lua activation of inventory objects --- files/data/scripts/omw/activationhandlers.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/files/data/scripts/omw/activationhandlers.lua b/files/data/scripts/omw/activationhandlers.lua index edadc30f79..a70696824f 100644 --- a/files/data/scripts/omw/activationhandlers.lua +++ b/files/data/scripts/omw/activationhandlers.lua @@ -33,6 +33,9 @@ local function onActivate(obj, actor) if world.isWorldPaused() then return end + if obj.parentContainer then + return + end local handlers = handlersPerObject[obj.id] if handlers then for i = #handlers, 1, -1 do From e549490bb1724d65ef67bf9717a11dbc75cdc0c6 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sun, 7 Apr 2024 08:28:43 -0500 Subject: [PATCH 357/451] record crime ID --- apps/openmw/mwlua/types/player.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index e517bdf77d..b2befe89de 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -192,6 +192,8 @@ namespace MWLua throw std::runtime_error("Only global scripts can change crime level"); const MWWorld::Class& cls = o.ptr().getClass(); cls.getNpcStats(o.ptr()).setBounty(amount); + if (amount == 0) + MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); }; player["isCharGenFinished"] = [](const Object&) -> bool { return MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1; From d23c10622d143043dbb1415b55b8798dfe05ac48 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 7 Apr 2024 21:02:54 +0100 Subject: [PATCH 358/451] Use dynamic cast to check for bone --- components/resource/scenemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 3b7deeaab3..bf2674a98f 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -273,7 +273,7 @@ namespace Resource { // If an osgAnimation bone/transform, ensure underscores in name are replaced with spaces // this is for compatibility reasons - if (node.libraryName() == std::string_view("osgAnimation") && node.className() == std::string_view("Bone")) + if (dynamic_cast(&node)) node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); if (osg::StateSet* stateset = node.getStateSet()) From f8224b29d4f050c699fb3e7663e9e0ef15673e4d Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Apr 2024 21:59:54 +0100 Subject: [PATCH 359/451] Resolve merge conflicts These were caused by https://gitlab.com/OpenMW/openmw/-/merge_requests/4005 I've had to cherry-pick parts of that MR into this commit, otherwise the CI would yell at me when it noticed the location tags had gone. --- CI/check_qt_translations.sh | 6 +- CMakeLists.txt | 6 +- files/lang/components_ru.ts | 2 +- files/lang/launcher_ru.ts | 6 +- files/lang/wizard_de.ts | 186 +---------------------------------- files/lang/wizard_fr.ts | 190 +----------------------------------- files/lang/wizard_ru.ts | 186 +---------------------------------- 7 files changed, 18 insertions(+), 564 deletions(-) diff --git a/CI/check_qt_translations.sh b/CI/check_qt_translations.sh index f3a82ed2e6..1fc2e19002 100755 --- a/CI/check_qt_translations.sh +++ b/CI/check_qt_translations.sh @@ -4,8 +4,8 @@ set -o pipefail LUPDATE="${LUPDATE:-lupdate}" -${LUPDATE:?} apps/wizard -ts files/lang/wizard_*.ts -${LUPDATE:?} apps/launcher -ts files/lang/launcher_*.ts -${LUPDATE:?} components/contentselector components/process -ts files/lang/components_*.ts +${LUPDATE:?} -locations none apps/wizard -ts files/lang/wizard_*.ts +${LUPDATE:?} -locations none apps/launcher -ts files/lang/launcher_*.ts +${LUPDATE:?} -locations none components/contentselector components/process -ts files/lang/components_*.ts ! (git diff --name-only | grep -q "^") || (echo -e "\033[0;31mBuild a 'translations' CMake target to update Qt localization for these files:\033[0;0m"; git diff --name-only | xargs -i echo -e "\033[0;31m{}\033[0;0m"; exit -1) diff --git a/CMakeLists.txt b/CMakeLists.txt index 76aede04c9..8a0a59bf4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1089,17 +1089,17 @@ if (USE_QT) file(GLOB COMPONENTS_TS_FILES ${CMAKE_SOURCE_DIR}/files/lang/components_*.ts) get_target_property(QT_LUPDATE_EXECUTABLE Qt::lupdate IMPORTED_LOCATION) add_custom_target(translations - COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/components/contentselector ${CMAKE_SOURCE_DIR}/components/process -ts ${COMPONENTS_TS_FILES} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/components/contentselector ${CMAKE_SOURCE_DIR}/components/process -ts ${COMPONENTS_TS_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/components VERBATIM COMMAND_EXPAND_LISTS - COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/apps/wizard -ts ${WIZARD_TS_FILES} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/apps/wizard -ts ${WIZARD_TS_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/wizard VERBATIM COMMAND_EXPAND_LISTS - COMMAND ${QT_LUPDATE_EXECUTABLE} ${CMAKE_SOURCE_DIR}/apps/launcher -ts ${LAUNCHER_TS_FILES} + COMMAND ${QT_LUPDATE_EXECUTABLE} -locations none ${CMAKE_SOURCE_DIR}/apps/launcher -ts ${LAUNCHER_TS_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/apps/launcher VERBATIM COMMAND_EXPAND_LISTS) diff --git a/files/lang/components_ru.ts b/files/lang/components_ru.ts index b16168effb..d70ebe320a 100644 --- a/files/lang/components_ru.ts +++ b/files/lang/components_ru.ts @@ -73,7 +73,7 @@ Arguments: Параметры: - + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 843222a423..5c044d38d4 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -109,7 +109,7 @@ &New Content List - Новый список плагинов + &Новый список плагинов Clone Content List @@ -547,7 +547,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <br><b>Could not create directory %0</b><br><br>%1<br> - <br><b>Не удалось создать директорию %0</b><br><br> + <br><b>Не удалось создать директорию %0</b><br><br>%1<br> @@ -1066,7 +1066,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Size of characters in game texts. - Размер символов в текстах + Размер символов в текстах. Font size diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts index 4fecd1de72..d793175dcb 100644 --- a/files/lang/wizard_de.ts +++ b/files/lang/wizard_de.ts @@ -4,27 +4,22 @@ ComponentSelectionPage - WizardPage - Select Components - Which components should be installed? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> - Selected components: @@ -32,17 +27,14 @@ ConclusionPage - WizardPage - Completing the OpenMW Wizard - Placeholder @@ -50,27 +42,22 @@ ExistingInstallationPage - WizardPage - Select Existing Installation - Select an existing installation for OpenMW to use or modify. - Detected installations: - Browse... @@ -78,42 +65,34 @@ ImportPage - WizardPage - Import Settings - Import settings from the Morrowind installation. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> - Import settings from Morrowind.ini - Import add-on and plugin selection - Import bitmap fonts setup from Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. @@ -123,17 +102,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationPage - WizardPage - Installing - Please wait while Morrowind is installed on your computer. @@ -141,32 +117,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationTargetPage - WizardPage - Select Installation Destination - Where should Morrowind be installed? - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. - Browse... @@ -174,17 +144,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov IntroPage - WizardPage - Welcome to the OpenMW Wizard - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. @@ -192,27 +159,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov LanguageSelectionPage - WizardPage - Select Morrowind Language - What is the language of the Morrowind installation? - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. @@ -220,62 +182,50 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov MethodSelectionPage - WizardPage - Select Installation Method - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. - Existing Installation - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. - Don't have a copy? - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game @@ -283,42 +233,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> - B&rowse... - Select configuration file - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> - Most recent Morrowind not detected - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? @@ -326,57 +268,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ComponentSelectionPage - &Install - &Skip - Morrowind (installed) - Morrowind - Tribunal (installed) - Tribunal - Bloodmoon (installed) - Bloodmoon - About to install Tribunal after Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> - Re-install &Bloodmoon @@ -384,17 +315,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> @@ -402,32 +330,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ExistingInstallationPage - No existing installations detected - Error detecting Morrowind configuration - Morrowind configuration file (*.ini) - Select Morrowind.esm (located in Data Files) - Morrowind master file (Morrowind.esm) - Error detecting Morrowind files @@ -435,78 +357,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationPage - <p>Attempting to install component %1.</p> - Attempting to install component %1. - %1 Installation - Select %1 installation media - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> - <p>Detected old version of component Morrowind.</p> - Detected old version of component Morrowind. - Morrowind Installation - Installation finished - Installation completed successfully! - Installation failed! - <p><br/><span style="color:red;"><b>Error: %1</b></p> - <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> - An error occurred @@ -514,37 +420,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationTargetPage - Error creating destination - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> - Insufficient permissions - Destination not empty - Select where to install Morrowind @@ -552,37 +451,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::LanguageSelectionPage - English - French - German - Italian - Polish - Russian - Spanish @@ -590,267 +482,193 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - OpenMW Wizard - - Error opening Wizard log file - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - - Error opening OpenMW configuration file - Quit Wizard - Are you sure you want to exit the Wizard? - Error creating OpenMW configuration directory - - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + Error writing OpenMW configuration file - - - Error writing OpenMW configuration file + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! - - Opening %1 failed: %2. - Failed to write Morrowind configuration file! - Writing to %1 failed: %2. - Installing: %1 - - Installing: %1 directory - Installation finished! - - Component parameter is invalid! - - An invalid component parameter was supplied. - Failed to find a valid archive containing %1.bsa! Retrying. - Installing %1 - Installation media path not set! - The source path for %1 was not set. - - Cannot create temporary directory! - - Failed to create %1. - Cannot move into temporary directory! - Failed to move into %1. - Moving installation files - - - - Could not install directory! - - - - Installing %1 to %2 failed. - Could not install translation file! - Failed to install *%1 files. - Could not install Morrowind data file! - - Failed to install %1. - Could not install Morrowind configuration file! - Installing: Sound directory - Could not find Tribunal data file! - - - Failed to find %1. - Could not find Tribunal patch file! - Could not find Bloodmoon data file! - Updating Morrowind configuration file - %1 installation finished! - Extracting: %1 - - - Failed to open InstallShield Cabinet File. - - - Opening %1 failed. - Failed to extract %1. - Complete path: %1 diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index c2950c0a42..ba3c8aaa19 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -4,27 +4,22 @@ ComponentSelectionPage - WizardPage - Select Components - Which components should be installed? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> - Selected components: @@ -32,17 +27,14 @@ ConclusionPage - WizardPage - Completing the OpenMW Wizard - Placeholder @@ -50,27 +42,22 @@ ExistingInstallationPage - WizardPage - Select Existing Installation - Select an existing installation for OpenMW to use or modify. - Detected installations: - Browse... @@ -78,42 +65,34 @@ ImportPage - WizardPage - Import Settings - Import settings from the Morrowind installation. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> - Import settings from Morrowind.ini - Import add-on and plugin selection - Import bitmap fonts setup from Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. @@ -123,17 +102,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationPage - WizardPage - Installing - Please wait while Morrowind is installed on your computer. @@ -141,32 +117,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationTargetPage - WizardPage - Select Installation Destination - Where should Morrowind be installed? - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. - Browse... @@ -174,17 +144,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov IntroPage - WizardPage - Welcome to the OpenMW Wizard - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. @@ -192,27 +159,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov LanguageSelectionPage - WizardPage - Select Morrowind Language - What is the language of the Morrowind installation? - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. @@ -220,62 +182,50 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov MethodSelectionPage - WizardPage - Select Installation Method - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. - Existing Installation - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. - Don't have a copy? - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game @@ -283,42 +233,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> - B&rowse... - Select configuration file - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> - Most recent Morrowind not detected - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? @@ -326,57 +268,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ComponentSelectionPage - &Install - &Skip - Morrowind (installed) - Morrowind - Tribunal (installed) - Tribunal - Bloodmoon (installed) - Bloodmoon - About to install Tribunal after Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> - Re-install &Bloodmoon @@ -384,17 +315,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> @@ -402,32 +330,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ExistingInstallationPage - No existing installations detected - Error detecting Morrowind configuration - Morrowind configuration file (*.ini) - Select Morrowind.esm (located in Data Files) - Morrowind master file (Morrowind.esm) - Error detecting Morrowind files @@ -435,78 +357,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationPage - <p>Attempting to install component %1.</p> - Attempting to install component %1. - %1 Installation - Select %1 installation media - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> - <p>Detected old version of component Morrowind.</p> - Detected old version of component Morrowind. - Morrowind Installation - Installation finished - Installation completed successfully! - Installation failed! - <p><br/><span style="color:red;"><b>Error: %1</b></p> - <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> - An error occurred @@ -514,37 +420,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationTargetPage - Error creating destination - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> - Insufficient permissions - Destination not empty - Select where to install Morrowind @@ -552,37 +451,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::LanguageSelectionPage - English - French - German - Italian - Polish - Russian - Spanish @@ -590,267 +482,193 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - OpenMW Wizard - - Error opening Wizard log file - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - - - Error opening OpenMW configuration file - Quit Wizard - - Are you sure you want to exit the Wizard? + Error writing OpenMW configuration file - - Error creating OpenMW configuration directory + Are you sure you want to exit the Wizard? - - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + Error creating OpenMW configuration directory - - - Error writing OpenMW configuration file + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! - - Opening %1 failed: %2. - Failed to write Morrowind configuration file! - Writing to %1 failed: %2. - Installing: %1 - - Installing: %1 directory - Installation finished! - - Component parameter is invalid! - - An invalid component parameter was supplied. - Failed to find a valid archive containing %1.bsa! Retrying. - Installing %1 - Installation media path not set! - The source path for %1 was not set. - - Cannot create temporary directory! - - Failed to create %1. - Cannot move into temporary directory! - Failed to move into %1. - Moving installation files - - - - Could not install directory! - - - - Installing %1 to %2 failed. - Could not install translation file! - Failed to install *%1 files. - Could not install Morrowind data file! - - Failed to install %1. - Could not install Morrowind configuration file! - Installing: Sound directory - Could not find Tribunal data file! - - - Failed to find %1. - Could not find Tribunal patch file! - Could not find Bloodmoon data file! - Updating Morrowind configuration file - %1 installation finished! - Extracting: %1 - - - Failed to open InstallShield Cabinet File. - - - Opening %1 failed. - Failed to extract %1. - Complete path: %1 diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts index 2461204681..0a2e6e5561 100644 --- a/files/lang/wizard_ru.ts +++ b/files/lang/wizard_ru.ts @@ -4,27 +4,22 @@ ComponentSelectionPage - WizardPage WizardPage - Select Components Выбор компонентов - Which components should be installed? Какие компоненты должны быть установлены? - <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> <html><head/><body><p>Выберите, какие дополнения для Morrowind нужно установить. Для достижения наилучших результатов рекомендуется установить оба дополнения.</p><p><span style=" font-weight:bold;">Подсказка:</span> Можно установить дополнения позже, запустив этот Мастер установки заново.<br/></p></body></html> - Selected components: Выбранные компоненты: @@ -32,17 +27,14 @@ ConclusionPage - WizardPage WizardPage - Completing the OpenMW Wizard Завершение работы Мастера установки OpenMW - Placeholder Placeholder @@ -50,27 +42,22 @@ ExistingInstallationPage - WizardPage WizardPage - Select Existing Installation Выбрать установленную копию игры - Select an existing installation for OpenMW to use or modify. Выбрать установленную копию игры для использования или изменения через OpenMW. - Detected installations: Обнаруженные установленные копии: - Browse... Выбрать... @@ -78,42 +65,34 @@ ImportPage - WizardPage WizardPage - Import Settings Импортировать настройки - Import settings from the Morrowind installation. Импортировать настройки из установленной копии Morrowind. - <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> <html><head/><body><p>Чтобы OpenMW мог работать правильно, ему нужно импортировать настройки из файла с настройками Morrowind.</p><p><span style=" font-weight:bold;">Подсказка:</span> Также можно импортировать настройки позже, запустив Мастер импорта заново.</p><p/></body></html> - Import settings from Morrowind.ini Импортировать настройки из Morrowind.ini - Import add-on and plugin selection Импортировать список подключенных плагинов - Import bitmap fonts setup from Morrowind.ini Импортировать растровые шрифты из Morrowind.ini - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. @@ -125,17 +104,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationPage - WizardPage WizardPage - Installing Установка - Please wait while Morrowind is installed on your computer. Пожалуйста, подождите, пока Morrowind устанавливается на ваш компьютер. @@ -143,32 +119,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov InstallationTargetPage - WizardPage WizardPage - Select Installation Destination Выберите путь для установки - Where should Morrowind be installed? Куда нужно установить Morrowind? - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. - Morrowind будет установлен в следующее место. + Morrowind будет установлен в следующее место. - Browse... Выбрать... @@ -176,17 +146,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov IntroPage - WizardPage WizardPage - Welcome to the OpenMW Wizard Добро пожаловать в Мастер установки - This Wizard will help you install Morrowind and its add-ons for OpenMW to use. Этот Мастер поможет вам установить Morrowind и его дополнения, чтобы OpenMW мог их использовать. @@ -194,27 +161,22 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov LanguageSelectionPage - WizardPage WizardPage - Select Morrowind Language Выберите язык вашей копии Morrowind - What is the language of the Morrowind installation? Какой язык использует ваша копия Morrowind? - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> ><html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. Выберите язык, используемый вашей копией Morrowind. @@ -222,62 +184,50 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov MethodSelectionPage - WizardPage WizardPage - Select Installation Method Выберите способ установки - <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> <html><head/><body><p>Выберите способ установки <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - Retail CD/DVD CD/DVD-диск - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. - Установить игру с диска + Установить игру с диска. - Existing Installation Установленная копия игры - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. Выбрать установленную копию игры. - Don't have a copy? Нет копии игры? - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game Купить игру @@ -285,42 +235,34 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov QObject - <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> <br><b>Не удалось найти Morrowind.ini</b><br><br>Мастеру требуется обновить настройки в этом файле.<br><br>Нажмите "Выбрать...", чтобы задать местоположение файла вручную.<br> - B&rowse... &Выбрать... - Select configuration file Выберите файл с настройками - <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. <b>Morrowind.bsa</b> не найден!<br>Убедитесь, что Morrowind был установлен правильно. - <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> <br><b>Может существовать более свежая версия Morrowind.</b><br><br>Все равно продолжить?<br> - Most recent Morrowind not detected Актуальная версия Morrowind не найдена - Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. Выберите корректный установочный дистрибутив %1.<br><b>Подсказка</b>: он должен содержать как минимум один <b>.cab</b>-файл. - There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? Может существовать более свежая версия Morrowind.<br><br>Все равно продолжить? @@ -328,57 +270,46 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ComponentSelectionPage - &Install &Установить - &Skip &Пропустить - Morrowind (installed) Morrowind (установлен) - Morrowind Morrowind - Tribunal (installed) Tribunal (установлен) - Tribunal Tribunal - Bloodmoon (installed) Bloodmoon (установлен) - Bloodmoon Bloodmoon - About to install Tribunal after Bloodmoon Попытка установить Tribunal после Bloodmoon - <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> <html><head/><body><p><b>Вы собираетесь установить Tribunal</b></p><p>Bloodmoon уже установлен на ваш компьютер.</p><p>Tribunal рекомендуется устанавлить перед установкой Bloodmoon.</p><p>Желаете ли вы переустановить Bloodmoon?</p></body></html> - Re-install &Bloodmoon Переустановить &Bloodmoon @@ -386,17 +317,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ConclusionPage - <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> <html><head/><body><p>Мастер OpenMW успешно установил Morrowind на ваш компьютер.</p></body></html> - <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> <html><head/><body><p>Мастер OpenMW успешно завершил изменение вашей установленной копии Morrowind.</body></html> - <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> <html><head/><body><p>Мастеру OpenMW не удалось установить Morrowind на ваш компьютер.</p><p>Пожалуйста, сообщите о встреченных вами ошибках на наш <a href="https://gitlab.com/OpenMW/openmw/issues">багтрекер</a>.<br/>Не забудьте включить туда лог установки.</p><br/></body></html> @@ -404,32 +332,26 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::ExistingInstallationPage - No existing installations detected Установленные копии игры не найдены - Error detecting Morrowind configuration Попытка найти настройки Morrowind завершилась ошибкой - Morrowind configuration file (*.ini) Файл настроек Morrowind (*.ini) - Select Morrowind.esm (located in Data Files) Выберите Morrowind.esm (расположен в Data Files) - Morrowind master file (Morrowind.esm) Мастер-файл Morrowind (Morrowind.esm) - Error detecting Morrowind files Не удалось обнаружить файлы Morrowind @@ -437,78 +359,62 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationPage - <p>Attempting to install component %1.</p> <p>Попытка установить компонент %1.</p> - Attempting to install component %1. Попытка установить компонент %1. - %1 Installation Установка %1 - Select %1 installation media Выберите установочный дистрибутив %1 - - <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> <p><br/><span style="color:red;"><b>Ошибка: Установка была прервана пользователем</b></span></p> - <p>Detected old version of component Morrowind.</p> lt;p>Обнаружена устаревшая версия компонента Morrowind.</p> - Detected old version of component Morrowind. Обнаружена устаревшая версия компонента Morrowind. - Morrowind Installation Установка Morrowind - Installation finished Установка завершена - Installation completed successfully! Установка успешно завершена! - Installation failed! Установка не удалась! - <p><br/><span style="color:red;"><b>Error: %1</b></p> <p><br/><span style="color:red;"><b>Ошибка: %1</b></p> - <p><span style="color:red;"><b>%1</b></p> <p><span style="color:red;"><b>%1</b></p> - <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> <html><head/><body><p><b>При работе Мастера возникла ошибка</b></p><p>Обнаруженная ошибка:</p><p>%1</p><p>Нажмите &quot;Показать детали...&quot; для получения дополнительной информации.</p></body></html> - An error occurred Произошла ошибка @@ -516,37 +422,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::InstallationTargetPage - Error creating destination Не удалось создать директорию назначения - <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> <html><head/><body><p><b>Не удалось создать директорию назначения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку, или же выберите другую директорию.</p></body> - <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> <html><head/><body><p><b>Не удалось записать данные в директорию назначения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку, или же выберите другую директорию.</p></body></html> - <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> <html><head/><body><p><b>Директория назначения содержит файлы</b></p><p>В указанной директории найдена установленная копия Morrowind.</p><p>Пожалуйста, выберите другую директорию, или же вернитесь на предыдущий шаг и выберите подключение установленной копии игры.</p></body></html> - Insufficient permissions Не хватает прав доступа - Destination not empty Выбранная директория не пустая - Select where to install Morrowind Выберите, куда установить Morrowind @@ -554,37 +453,30 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::LanguageSelectionPage - English Английский - French Французский - German Немецкий - Italian Итальянский - Polish Польский - Russian Русский - Spanish Испанский @@ -592,59 +484,42 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::MainWizard - OpenMW Wizard Мастер OpenMW - - Error opening Wizard log file Не удалось открыть лог-файл Мастера - - - <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось открыть %1 для записи</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось открыть %1 для чтения</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - - Error opening OpenMW configuration file Не удалось открыть файл с настройками OpenMW - Quit Wizard Завершить работу Мастера - Are you sure you want to exit the Wizard? Вы уверены, что хотите завершить работу Мастера? - Error creating OpenMW configuration directory Не удалось создать директорию для настроек OpenMW - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> <html><head/><body><p><b>Не удалось создать %1</b></p><p>Пожалуйста, проверьте права доступа и повторите попытку.</p></body></html> - - Error writing OpenMW configuration file Не удалось записать данные в файл с настройками OpenMW @@ -652,207 +527,150 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Wizard::UnshieldWorker - - Failed to open Morrowind configuration file! Не удалось открыть файл с настройками Morrowind! - - Opening %1 failed: %2. Попытка открыть %1 не удалась: %2. - Failed to write Morrowind configuration file! Не удалось записать данные в файл с настройками Morrowind! - Writing to %1 failed: %2. Запись в %1 завершилась с ошибкой: %2. - Installing: %1 Установка: %1 - - Installing: %1 directory Установка: директория %1 - Installation finished! Установка завершена! - - Component parameter is invalid! Некорректный параметр для компонента! - - An invalid component parameter was supplied. Задан некорректный параметр для компонента. - Failed to find a valid archive containing %1.bsa! Retrying. Не удалось найти архив, содержащий %1.bsa! Повторная попытка. - Installing %1 Установка %1 - Installation media path not set! путь к установочному дистрибутиву не задан! - The source path for %1 was not set. Исходный пусть для %1 не задан. - - Cannot create temporary directory! Не удалось создать временную директорию! - - Failed to create %1. Не удалось создать %1. - Cannot move into temporary directory! Не удалось переместить во временную директорию! - Failed to move into %1. Не удалось переместить в %1. - Moving installation files Перемещение файлов установки - - - - Could not install directory! Не удалось установить директорию! - - - - Installing %1 to %2 failed. Не удалось установить %1 в %2. - Could not install translation file! Не удалось установить файл с переводом! - Failed to install *%1 files. Не удалось установить файлы *%1. - Could not install Morrowind data file! Не удалось установить файл с данными Morrowind! - - Failed to install %1. Не удалось установить %1. - Could not install Morrowind configuration file! Не удалось установить файл с настройками Morrowind! - Installing: Sound directory Установка: директория Sound - Could not find Tribunal data file! Не удалось найти файл с данными Tribunal! - - - Failed to find %1. Не удалось найти %1. - Could not find Tribunal patch file! Не удалось найти файл с патчем для Tribunal! - Could not find Bloodmoon data file! Не удалось найти файл с данными Bloodmoon! - Updating Morrowind configuration file Обновление файла с настройками Morrowind - %1 installation finished! Установка %1 завершена! - Extracting: %1 Извлечение: %1 - - - Failed to open InstallShield Cabinet File. Не удалось открыть файл InstallShield Cabinet. - - - Opening %1 failed. Не удалось открыть %1. - Failed to extract %1. Не удалось извлечь %1. - Complete path: %1 Полный путь: %1 From 48f1f085378e84d802d928de53a405dff0cc0bcd Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Apr 2024 22:12:39 +0100 Subject: [PATCH 360/451] Hide things that depend on present-but-inactive game files https://gitlab.com/OpenMW/openmw/-/merge_requests/3925#note_1843962919 --- components/contentselector/model/contentmodel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 66fde2063f..8e7b77d308 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -110,7 +110,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index return Qt::NoItemFlags; if (file->builtIn() || file->fromAnotherConfigFile()) - return Qt::NoItemFlags; + return Qt::ItemIsEnabled; // game files can always be checked if (file == mGameFile) @@ -228,7 +228,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int { if (file == mGameFile) return ContentType_GameFile; - else + else if (flags(index)) return ContentType_Addon; break; From 810e2c44a6d5cf1a131f15626b6365fc9c135486 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 7 Apr 2024 22:34:17 +0100 Subject: [PATCH 361/451] Merge conflict resolution part II --- files/lang/wizard_de.ts | 4 ++-- files/lang/wizard_fr.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts index d793175dcb..5749cf2d5d 100644 --- a/files/lang/wizard_de.ts +++ b/files/lang/wizard_de.ts @@ -514,11 +514,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Error writing OpenMW configuration file + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + Error writing OpenMW configuration file diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index ba3c8aaa19..9b5acbc9e9 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -505,10 +505,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Quit Wizard - - Error writing OpenMW configuration file - - Are you sure you want to exit the Wizard? @@ -521,6 +517,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + + Error writing OpenMW configuration file + + Wizard::UnshieldWorker From edb9da5a67ec46544d6aefb04e8521d331b9702f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 21 Feb 2024 10:28:01 +0400 Subject: [PATCH 362/451] Capitalize in-game setting --- files/data/l10n/OMWEngine/de.yaml | 2 +- files/data/l10n/OMWEngine/en.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/files/data/l10n/OMWEngine/de.yaml b/files/data/l10n/OMWEngine/de.yaml index aab58fb30c..142ab44994 100644 --- a/files/data/l10n/OMWEngine/de.yaml +++ b/files/data/l10n/OMWEngine/de.yaml @@ -161,7 +161,7 @@ WindowModeWindowedFullscreen: "Fenster Vollbild" #GammaCorrection: "Gamma Correction" #GammaDark: "Dark" #GammaLight: "Light" -#GmstOverridesL10n: "Strings from ESM files have priority" +#GmstOverridesL10n: "Strings From ESM Files Have Priority" #InvertYAxis: "Invert Y Axis" #MenuHelpDelay: "Menu Help Delay" #MenuTransparency: "Menu Transparency" diff --git a/files/data/l10n/OMWEngine/en.yaml b/files/data/l10n/OMWEngine/en.yaml index d14aaa78fa..37f9486083 100644 --- a/files/data/l10n/OMWEngine/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -94,7 +94,7 @@ FrameRateHint: "Hint: press F3 to show\nthe current frame rate." GammaCorrection: "Gamma Correction" GammaDark: "Dark" GammaLight: "Light" -GmstOverridesL10n: "Strings from ESM files have priority" +GmstOverridesL10n: "Strings From ESM Files Have Priority" InvertXAxis: "Invert X Axis" InvertYAxis: "Invert Y Axis" Language: "Language" From e8c3c8115a4954e826faa38656c07c583cbbaded Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 21 Feb 2024 10:17:02 +0400 Subject: [PATCH 363/451] Capitalize captions in Launcher and Wizard --- apps/launcher/ui/datafilespage.ui | 10 +- apps/launcher/ui/graphicspage.ui | 10 +- apps/launcher/ui/importpage.ui | 6 +- apps/launcher/ui/settingspage.ui | 224 ++++++------- apps/wizard/ui/importpage.ui | 6 +- files/lang/launcher_de.ts | 510 +++++++++++++++--------------- files/lang/launcher_fr.ts | 510 +++++++++++++++--------------- files/lang/launcher_ru.ts | 226 ++++++------- files/lang/wizard_de.ts | 6 +- files/lang/wizard_fr.ts | 6 +- files/lang/wizard_ru.ts | 8 +- 11 files changed, 761 insertions(+), 761 deletions(-) diff --git a/apps/launcher/ui/datafilespage.ui b/apps/launcher/ui/datafilespage.ui index 2b54307838..3c9967ef15 100644 --- a/apps/launcher/ui/datafilespage.ui +++ b/apps/launcher/ui/datafilespage.ui @@ -36,7 +36,7 @@ - <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -160,7 +160,7 @@ - <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -251,7 +251,7 @@ - <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -305,7 +305,7 @@ - Remove unused tiles + Remove Unused Tiles true @@ -317,7 +317,7 @@ - Max size + Max Size diff --git a/apps/launcher/ui/graphicspage.ui b/apps/launcher/ui/graphicspage.ui index fa92c7b789..b3e2b15e39 100644 --- a/apps/launcher/ui/graphicspage.ui +++ b/apps/launcher/ui/graphicspage.ui @@ -26,7 +26,7 @@ - Window mode + Window Mode @@ -111,14 +111,14 @@ - Framerate limit + Framerate Limit - Window border + Window Border @@ -207,14 +207,14 @@ - Anti-aliasing + Anti-Aliasing - Vertical synchronization + Vertical Synchronization diff --git a/apps/launcher/ui/importpage.ui b/apps/launcher/ui/importpage.ui index 4626d29e8a..0b5d014afa 100644 --- a/apps/launcher/ui/importpage.ui +++ b/apps/launcher/ui/importpage.ui @@ -54,7 +54,7 @@ - File to import settings from: + File to Import Settings From: @@ -73,7 +73,7 @@ - Import add-on and plugin selection (creates a new Content List) + Import Add-on and Plugin Selection true @@ -88,7 +88,7 @@ so OpenMW provides another set of fonts to avoid these issues. These fonts use T to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. - Import bitmap fonts setup + Import Bitmap Fonts true diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 665c9e9712..14aa0118cb 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -39,7 +39,7 @@ <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> - Always allow actors to follow over water + Always Allow Actors to Follow over Water @@ -49,7 +49,7 @@ <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> - Permanent barter disposition changes + Permanent Barter Disposition Changes @@ -59,7 +59,7 @@ <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> - Racial variation in speed fix + Racial Variation in Speed Fix @@ -69,7 +69,7 @@ <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> - Classic Calm spells behavior + Classic Calm Spells Behavior @@ -79,7 +79,7 @@ <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> - NPCs avoid collisions + NPCs Avoid Collisions @@ -89,7 +89,7 @@ <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> - Soulgem values rebalance + Soulgem Values Rebalance @@ -99,7 +99,7 @@ <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - Day night switch nodes + Day Night Switch Nodes @@ -109,7 +109,7 @@ <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - Followers defend immediately + Followers Defend Immediately @@ -119,7 +119,7 @@ <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> - Use navigation mesh for pathfinding + Use Navigation Mesh for Pathfinding @@ -129,7 +129,7 @@ <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> - Only magical ammo bypass resistance + Only Magical Ammo Bypass Resistance @@ -139,7 +139,7 @@ <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - Graphic herbalism + Graphic Herbalism @@ -149,7 +149,7 @@ <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> - Swim upward correction + Swim Upward Correction @@ -159,7 +159,7 @@ <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> - Enchanted weapons are magical + Enchanted Weapons Are Magical @@ -169,7 +169,7 @@ <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - Merchant equipping fix + Merchant Equipping Fix @@ -179,7 +179,7 @@ <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> - Trainers choose offered skills by base value + Trainers Choose Offered Skills by Base Value @@ -189,7 +189,7 @@ <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> - Can loot during death animation + Can Loot During Death Animation @@ -199,7 +199,7 @@ <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - Steal from knocked out actors in combat + Steal from Knocked out Actors in Combat @@ -209,7 +209,7 @@ <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> - Classic reflected Absorb spells behavior + Classic Reflected Absorb Spells Behavior @@ -219,7 +219,7 @@ <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> - Unarmed creature attacks damage armor + Unarmed Creature Attacks Damage Armor @@ -230,7 +230,7 @@ - Factor strength into hand-to-hand combat + Factor Strength into Hand-to-Hand Combat @@ -246,12 +246,12 @@ - Affect werewolves + Affect Werewolves - Do not affect werewolves + Do Not Affect Werewolves @@ -262,7 +262,7 @@ <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - Background physics threads + Background Physics Threads @@ -272,7 +272,7 @@ - Actor collision shape type + Actor Collision Shape Type @@ -282,16 +282,16 @@ Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. - Axis-aligned bounding box + Axis-Aligned Bounding Box - Axis-aligned bounding box + Axis-Aligned Bounding Box - Rotating box + Rotating Box @@ -343,7 +343,7 @@ <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> - Smooth movement + Smooth Movement @@ -353,7 +353,7 @@ <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - Use additional animation sources + Use Additional Animation Sources @@ -376,7 +376,7 @@ <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> - Turn to movement direction + Turn to Movement Direction @@ -389,7 +389,7 @@ <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - Weapon sheathing + Weapon Sheathing @@ -402,7 +402,7 @@ <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - Shield sheathing + Shield Sheathing @@ -412,7 +412,7 @@ <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> - Player movement ignores animation + Player Movement Ignores Animation @@ -422,7 +422,7 @@ <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - Use magic item animation + Use Magic Item Animation @@ -447,7 +447,7 @@ If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> - Auto use object normal maps + Auto Use Object Normal Maps @@ -457,7 +457,7 @@ <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> - Soft particles + Soft Particles @@ -471,7 +471,7 @@ (.osg file, not supported in .nif files). Affects objects.</p></body></html> - Auto use object specular maps + Auto Use Object Specular Maps @@ -481,7 +481,7 @@ <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> - Auto use terrain normal maps + Auto Use Terrain Normal Maps @@ -504,7 +504,7 @@ <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> - Auto use terrain specular maps + Auto Use Terrain Specular Maps @@ -514,7 +514,7 @@ <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> - Adjust coverage for alpha test + Adjust Coverage for Alpha Test @@ -524,7 +524,7 @@ <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - Use anti-aliased alpha testing + Use Anti-Aliased Alpha Testing @@ -537,7 +537,7 @@ </p></body></html> - Bump/reflect map local lighting + Bump/Reflect Map Local Lighting @@ -547,7 +547,7 @@ <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> - Weather particle occlusion + Weather Particle Occlusion @@ -570,7 +570,7 @@ <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> - Exponential fog + Exponential Fog @@ -613,7 +613,7 @@ This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> - Radial fog + Radial Fog @@ -626,7 +626,7 @@ <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> - Sky blending start + Sky Blending Start @@ -636,7 +636,7 @@ <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> - Sky blending + Sky Blending @@ -657,7 +657,7 @@ <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> - Object paging min size + Object Paging Min Size @@ -680,7 +680,7 @@ - Viewing distance + Viewing Distance @@ -719,7 +719,7 @@ <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - Distant land + Distant Land @@ -729,7 +729,7 @@ <html><head/><body><p>Use object paging for active cells grid.</p></body></html> - Active grid object paging + Active Grid Object Paging @@ -744,16 +744,22 @@ - - + + false - - <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + + 3 - - Auto exposure speed + + 0.010000000000000 + + + 10.000000000000000 + + + 0.001000000000000 @@ -779,26 +785,20 @@ <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> - Transparent postpass + Transparent Postpass - - + + false - - 3 - - - 0.010000000000000 - - - 10.000000000000000 + + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> - - 0.001000000000000 + + Auto Exposure Speed @@ -808,7 +808,7 @@ <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> - Enable post processing + Enable Post Processing @@ -827,17 +827,17 @@ - bounds + Bounds - primitives + Primitives - none + None @@ -848,7 +848,7 @@ <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> - Shadow planes computation method + Shadow Planes Computation Method @@ -883,7 +883,7 @@ <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> - Enable actor shadows + Enable Actor Shadows @@ -917,7 +917,7 @@ <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> - Fade start multiplier + Fade Start Multiplier @@ -927,7 +927,7 @@ <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> - Enable player shadows + Enable Player Shadows @@ -937,7 +937,7 @@ <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> - Shadow map resolution + Shadow Map Resolution @@ -947,7 +947,7 @@ <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> - Shadow distance limit: + Shadow Distance Limit: @@ -957,7 +957,7 @@ <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> - Enable object shadows + Enable Object Shadows @@ -1002,7 +1002,7 @@ <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> - Enable indoor shadows + Enable Indoor Shadows @@ -1012,7 +1012,7 @@ <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> - Enable terrain shadows + Enable Terrain Shadows @@ -1033,7 +1033,7 @@ <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - Maximum light distance + Maximum Light Distance @@ -1056,7 +1056,7 @@ <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - Max lights + Max Lights @@ -1066,7 +1066,7 @@ <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - Fade start multiplier + Fade Start Multiplier @@ -1079,7 +1079,7 @@ <p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> - Lighting method + Lighting Method @@ -1108,7 +1108,7 @@ <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> - Bounding sphere multiplier + Bounding Sphere Multiplier @@ -1118,7 +1118,7 @@ <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - Minimum interior brightness + Minimum Interior Brightness @@ -1213,7 +1213,7 @@ Select your preferred audio device. - Audio device + Audio Device @@ -1299,7 +1299,7 @@ Select your preferred HRTF profile. - HRTF profile + HRTF Profile @@ -1335,7 +1335,7 @@ In third-person view, use the camera as the sound listener instead of the player character. - Use the camera as the sound listener + Use the Camera as the Sound Listener @@ -1383,7 +1383,7 @@ - Tooltip and crosshair + Tooltip and Crosshair @@ -1429,7 +1429,7 @@ <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - GUI scaling factor + GUI Scaling Factor @@ -1439,7 +1439,7 @@ <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - Show effect duration + Show Effect Duration @@ -1449,7 +1449,7 @@ <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - Change dialogue topic color + Change Dialogue Topic Color @@ -1459,7 +1459,7 @@ Size of characters in game texts. - Font size + Font Size @@ -1469,7 +1469,7 @@ <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - Can zoom on maps + Can Zoom on Maps @@ -1479,7 +1479,7 @@ <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - Show projectile damage + Show Projectile Damage @@ -1489,7 +1489,7 @@ <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - Show melee info + Show Melee Info @@ -1499,14 +1499,14 @@ <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - Stretch menu background + Stretch Menu Background - Show owned objects + Show Owned Objects @@ -1516,7 +1516,7 @@ <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - Show enchant chance + Show Enchant Chance @@ -1554,7 +1554,7 @@ <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - Add "Time Played" to saves + Add "Time Played" to Saves @@ -1563,7 +1563,7 @@ - Maximum quicksaves + Maximum Quicksaves @@ -1590,7 +1590,7 @@ - Screenshot format + Screenshot Format @@ -1618,7 +1618,7 @@ - Notify on saved screenshot + Notify on Saved Screenshot @@ -1668,14 +1668,14 @@ <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - Grab cursor + Grab Cursor - Skip menu and generate default character + Skip Menu and Generate Default Character @@ -1700,14 +1700,14 @@ - Start default character at + Start Default Character at - default cell + Default Cell @@ -1716,7 +1716,7 @@ - Run script after startup: + Run Script After Startup: diff --git a/apps/wizard/ui/importpage.ui b/apps/wizard/ui/importpage.ui index acc0d268ec..669920c5f3 100644 --- a/apps/wizard/ui/importpage.ui +++ b/apps/wizard/ui/importpage.ui @@ -33,7 +33,7 @@ - Import settings from Morrowind.ini + Import Settings From Morrowind.ini true @@ -43,7 +43,7 @@ - Import add-on and plugin selection + Import Add-on and Plugin Selection true @@ -53,7 +53,7 @@ - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 925733f9d3..e3101cf6df 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -7,18 +7,10 @@ Content Files - - <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Data Directories - - <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Scan directories for likely data directories and append them at the end of the list. @@ -67,10 +59,6 @@ Move selected archive one position up - - <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Move selected archive one position down @@ -95,14 +83,6 @@ Cancel - - Remove unused tiles - - - - Max size - - MiB @@ -159,6 +139,26 @@ Ctrl+R + + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + Remove Unused Tiles + + + + Max Size + + GraphicsPage @@ -227,27 +227,27 @@ - Window mode + Resolution - Framerate limit + Window Mode - Window border + Framerate Limit - Resolution + Window Border - Anti-aliasing + Anti-Aliasing - Vertical synchronization + Vertical Synchronization @@ -270,29 +270,29 @@ - File to import settings from: + Browse... - Browse... + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. - Import add-on and plugin selection (creates a new Content List) + Run &Settings Importer - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, -so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar -to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + File to Import Settings From: - Import bitmap fonts setup + Import Bitmap Fonts - Run &Settings Importer + Import Add-on and Plugin Selection @@ -607,848 +607,848 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Permanent barter disposition changes + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> - Followers defend immediately + Uncapped Damage Fatigue - <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> + <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> - Uncapped Damage Fatigue + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> - <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> + <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> - Classic Calm spells behavior + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> - Soulgem values rebalance + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> - <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> - Swim upward correction + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> - Enchanted weapons are magical + <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> - <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> + Off - Classic reflected Absorb spells behavior + <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> + Cylinder - Use navigation mesh for pathfinding + Visuals - <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + Animations - NPCs avoid collisions + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> - Racial variation in speed fix + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> - Can loot during death animation + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - Unarmed creature attacks damage armor + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> - Off + Shaders - Affect werewolves + <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately + (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> - Do not affect werewolves + <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> - <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> + <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately + (see 'specular map pattern', e.g. for a base texture foo.dds, + the specular map texture would have to be named foo_spec.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file + (.osg file, not supported in .nif files). Affects objects.</p></body></html> - Axis-aligned bounding box + <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> - Rotating box + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. + Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. + Affected objects will use shaders. + </p></body></html> - Cylinder + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - Visuals + <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> - Animations + <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> - <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> - Use magic item animation + Fog - <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> + <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. + This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> - Smooth movement + <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> - <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> - Use additional animation sources + <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> - <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> + Terrain - Turn to movement direction + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> - <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - Weapon sheathing + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> - <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> + <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - Shield sheathing + Post Processing - <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> - Player movement ignores animation + <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> - Shaders + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> - <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately - (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). - If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> + Audio - Auto use object normal maps + Select your preferred audio device. - <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> + Default - Auto use terrain normal maps + This setting controls HRTF, which simulates 3D sound on stereo systems. - <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately - (see 'specular map pattern', e.g. for a base texture foo.dds, - the specular map texture would have to be named foo_spec.dds). - If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file - (.osg file, not supported in .nif files). Affects objects.</p></body></html> + HRTF - Auto use object specular maps + Automatic - <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> + On - Auto use terrain specular maps + Select your preferred HRTF profile. - <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. - Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. - Affected objects will use shaders. - </p></body></html> + Interface - Bump/reflect map local lighting + <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - Use anti-aliased alpha testing + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> + Size of characters in game texts. - <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - Adjust coverage for alpha test + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - Fog + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. - This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> + <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - Radial fog + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - Exponential fog + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> - <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> + Miscellaneous - Sky blending + Saves - <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> + <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - Sky blending start + JPG - Terrain + PNG - Viewing distance + TGA - <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + Testing - Object paging min size + These settings are intended for testing mods and will cause issues if used for normal gameplay. - <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - Distant land + Browse… - <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. - Active grid object paging + Gameplay - <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> + <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> - Day night switch nodes + <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> - Post Processing + cells - <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> + Shadows - Enable post processing + <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> - <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> - Transparent postpass + unit(s) - <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> - Auto exposure speed + 512 - Audio + 1024 - Select your preferred audio device. + 2048 - Default + 4096 - This setting controls HRTF, which simulates 3D sound on stereo systems. + <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> - HRTF + <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> - Automatic + <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> - On + <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> - Select your preferred HRTF profile. + <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> - Interface + <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> - <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> - GUI scaling factor + Lighting - <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + Tooltip - Show effect duration + Crosshair - <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + Screenshots - Change dialogue topic color + <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> - Size of characters in game texts. + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> - Font size + <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - Can zoom on maps + <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + <html><head/><body><p>Set the internal handling of light sources.</p> +<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> +<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> +<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> - <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + Legacy - Show projectile damage + Shaders (compatibility) - <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> - Show melee info + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> + In third-person view, use the camera as the sound listener instead of the player character. - Stretch menu background + Permanent Barter Disposition Changes - Show owned objects + Classic Calm Spells Behavior - <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + NPCs Avoid Collisions - Show enchant chance + Soulgem Values Rebalance - <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + Day Night Switch Nodes - Merchant equipping fix + Followers Defend Immediately - <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + Only Magical Ammo Bypass Resistance - Miscellaneous + Graphic Herbalism - Saves + Swim Upward Correction - <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> + Enchanted Weapons Are Magical - Add "Time Played" to saves + Merchant Equipping Fix - JPG + Can Loot During Death Animation - PNG + Classic Reflected Absorb Spells Behavior - TGA + Unarmed Creature Attacks Damage Armor - Notify on saved screenshot + Affect Werewolves - Testing + Do Not Affect Werewolves - These settings are intended for testing mods and will cause issues if used for normal gameplay. + Background Physics Threads - <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + Actor Collision Shape Type - Grab cursor + Axis-Aligned Bounding Box - Skip menu and generate default character + Rotating Box - Start default character at + Smooth Movement - default cell + Use Additional Animation Sources - Run script after startup: + Weapon Sheathing - Browse… + Shield Sheathing - Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. + Player Movement Ignores Animation - Gameplay + Use Magic Item Animation - Always allow actors to follow over water + Auto Use Object Normal Maps - <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> + Soft Particles - Only magical ammo bypass resistance + Auto Use Object Specular Maps - Graphic herbalism + Auto Use Terrain Normal Maps - <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + Auto Use Terrain Specular Maps - Trainers choose offered skills by base value + Use Anti-Aliased Alpha Testing - Steal from knocked out actors in combat + Bump/Reflect Map Local Lighting - Factor strength into hand-to-hand combat + Weather Particle Occlusion - Background physics threads + Exponential Fog - Actor collision shape type + Radial Fog - Soft particles + Sky Blending Start - Weather particle occlusion + Sky Blending - cells + Object Paging Min Size - Shadows + Viewing Distance - bounds + Distant Land - primitives + Active Grid Object Paging - none + Transparent Postpass - <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> + Auto Exposure Speed - Shadow planes computation method + Enable Post Processing - <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + Shadow Planes Computation Method - unit(s) + Enable Actor Shadows - <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + Fade Start Multiplier - Enable actor shadows + Enable Player Shadows - 512 + Shadow Map Resolution - 1024 + Shadow Distance Limit: - 2048 + Enable Object Shadows - 4096 + Enable Indoor Shadows - <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> + Enable Terrain Shadows - Fade start multiplier + Maximum Light Distance - <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + Max Lights - Enable player shadows + Lighting Method - <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> + Bounding Sphere Multiplier - Shadow map resolution + Minimum Interior Brightness - <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> + Audio Device - Shadow distance limit: + HRTF Profile - <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + Tooltip and Crosshair - Enable object shadows + GUI Scaling Factor - <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + Show Effect Duration - Enable indoor shadows + Change Dialogue Topic Color - <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> + Font Size - Enable terrain shadows + Show Projectile Damage - Lighting + Show Melee Info - Lighting method + Stretch Menu Background - Audio device + Show Owned Objects - HRTF profile + Show Enchant Chance - Tooltip + Maximum Quicksaves - Crosshair + Screenshot Format - Tooltip and crosshair + Grab Cursor - Maximum quicksaves + Default Cell - Screenshots + Bounds - Screenshot format + Primitives - <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> + None - <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> + Always Allow Actors to Follow over Water - <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> + Racial Variation in Speed Fix - <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> + Use Navigation Mesh for Pathfinding - <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> + Trainers Choose Offered Skills by Base Value - <html><head/><body><p>Set the internal handling of light sources.</p> -<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> -<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> -<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> + Steal from Knocked out Actors in Combat - Legacy + Factor Strength into Hand-to-Hand Combat - Shaders (compatibility) + Turn to Movement Direction - <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> + Adjust Coverage for Alpha Test - <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + Use the Camera as the Sound Listener - In third-person view, use the camera as the sound listener instead of the player character. + Can Zoom on Maps - Use the camera as the sound listener + Add "Time Played" to Saves - Maximum light distance + Notify on Saved Screenshot - Max lights + Skip Menu and Generate Default Character - Bounding sphere multiplier + Start Default Character at - Minimum interior brightness + Run Script After Startup: diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 3471fc6c5c..89f5cb1c9b 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -7,18 +7,10 @@ Content Files - - <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Data Directories - - <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Scan directories for likely data directories and append them at the end of the list. @@ -67,10 +59,6 @@ Move selected archive one position up - - <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - - Move selected archive one position down @@ -95,14 +83,6 @@ Cancel - - Remove unused tiles - - - - Max size - - MiB @@ -159,6 +139,26 @@ Ctrl+R + + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + + + + Remove Unused Tiles + + + + Max Size + + GraphicsPage @@ -227,27 +227,27 @@ - Window mode + Resolution - Framerate limit + Window Mode - Window border + Framerate Limit - Resolution + Window Border - Anti-aliasing + Anti-Aliasing - Vertical synchronization + Vertical Synchronization @@ -270,29 +270,29 @@ - File to import settings from: + Browse... - Browse... + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. - Import add-on and plugin selection (creates a new Content List) + Run &Settings Importer - Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, -so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar -to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + File to Import Settings From: - Import bitmap fonts setup + Import Bitmap Fonts - Run &Settings Importer + Import Add-on and Plugin Selection @@ -607,848 +607,848 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Permanent barter disposition changes + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> - Followers defend immediately + Uncapped Damage Fatigue - <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> + <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> - Uncapped Damage Fatigue + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> - <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> + <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> - Classic Calm spells behavior + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> - Soulgem values rebalance + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> - <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> - Swim upward correction + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> - Enchanted weapons are magical + <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> - <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> + Off - Classic reflected Absorb spells behavior + <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> + Cylinder - Use navigation mesh for pathfinding + Visuals - <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + Animations - NPCs avoid collisions + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> - Racial variation in speed fix + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> - Can loot during death animation + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - Unarmed creature attacks damage armor + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> - Off + Shaders - Affect werewolves + <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately + (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> - Do not affect werewolves + <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> - <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> + <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately + (see 'specular map pattern', e.g. for a base texture foo.dds, + the specular map texture would have to be named foo_spec.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file + (.osg file, not supported in .nif files). Affects objects.</p></body></html> - Axis-aligned bounding box + <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> - Rotating box + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. + Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. + Affected objects will use shaders. + </p></body></html> - Cylinder + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - Visuals + <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> - Animations + <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> - <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> - Use magic item animation + Fog - <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> + <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. + This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> - Smooth movement + <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> - <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> - Use additional animation sources + <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> - <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> + Terrain - Turn to movement direction + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> - <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - Weapon sheathing + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> - <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> + <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - Shield sheathing + Post Processing - <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> - Player movement ignores animation + <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> - Shaders + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> - <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately - (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). - If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> + Audio - Auto use object normal maps + Select your preferred audio device. - <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> + Default - Auto use terrain normal maps + This setting controls HRTF, which simulates 3D sound on stereo systems. - <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately - (see 'specular map pattern', e.g. for a base texture foo.dds, - the specular map texture would have to be named foo_spec.dds). - If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file - (.osg file, not supported in .nif files). Affects objects.</p></body></html> + HRTF - Auto use object specular maps + Automatic - <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> + On - Auto use terrain specular maps + Select your preferred HRTF profile. - <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. - Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. - Affected objects will use shaders. - </p></body></html> + Interface - Bump/reflect map local lighting + <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - Use anti-aliased alpha testing + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> + Size of characters in game texts. - <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - Adjust coverage for alpha test + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - Fog + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. - This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> + <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - Radial fog + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - Exponential fog + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> - <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> + Miscellaneous - Sky blending + Saves - <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> + <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - Sky blending start + JPG - Terrain + PNG - Viewing distance + TGA - <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + Testing - Object paging min size + These settings are intended for testing mods and will cause issues if used for normal gameplay. - <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - Distant land + Browse… - <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. - Active grid object paging + Gameplay - <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> + <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> - Day night switch nodes + <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> - Post Processing + cells - <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> + Shadows - Enable post processing + <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> - <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> - Transparent postpass + unit(s) - <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> - Auto exposure speed + 512 - Audio + 1024 - Select your preferred audio device. + 2048 - Default + 4096 - This setting controls HRTF, which simulates 3D sound on stereo systems. + <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> - HRTF + <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> - Automatic + <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> - On + <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> - Select your preferred HRTF profile. + <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> - Interface + <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> - <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> - GUI scaling factor + Lighting - <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + Tooltip - Show effect duration + Crosshair - <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + Screenshots - Change dialogue topic color + <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> - Size of characters in game texts. + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> - Font size + <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - Can zoom on maps + <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + <html><head/><body><p>Set the internal handling of light sources.</p> +<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> +<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> +<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> - <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + Legacy - Show projectile damage + Shaders (compatibility) - <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> - Show melee info + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> + In third-person view, use the camera as the sound listener instead of the player character. - Stretch menu background + Permanent Barter Disposition Changes - Show owned objects + Classic Calm Spells Behavior - <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + NPCs Avoid Collisions - Show enchant chance + Soulgem Values Rebalance - <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + Day Night Switch Nodes - Merchant equipping fix + Followers Defend Immediately - <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + Only Magical Ammo Bypass Resistance - Miscellaneous + Graphic Herbalism - Saves + Swim Upward Correction - <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> + Enchanted Weapons Are Magical - Add "Time Played" to saves + Merchant Equipping Fix - JPG + Can Loot During Death Animation - PNG + Classic Reflected Absorb Spells Behavior - TGA + Unarmed Creature Attacks Damage Armor - Notify on saved screenshot + Affect Werewolves - Testing + Do Not Affect Werewolves - These settings are intended for testing mods and will cause issues if used for normal gameplay. + Background Physics Threads - <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + Actor Collision Shape Type - Grab cursor + Axis-Aligned Bounding Box - Skip menu and generate default character + Rotating Box - Start default character at + Smooth Movement - default cell + Use Additional Animation Sources - Run script after startup: + Weapon Sheathing - Browse… + Shield Sheathing - Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. + Player Movement Ignores Animation - Gameplay + Use Magic Item Animation - Always allow actors to follow over water + Auto Use Object Normal Maps - <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> + Soft Particles - Only magical ammo bypass resistance + Auto Use Object Specular Maps - Graphic herbalism + Auto Use Terrain Normal Maps - <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + Auto Use Terrain Specular Maps - Trainers choose offered skills by base value + Use Anti-Aliased Alpha Testing - Steal from knocked out actors in combat + Bump/Reflect Map Local Lighting - Factor strength into hand-to-hand combat + Weather Particle Occlusion - Background physics threads + Exponential Fog - Actor collision shape type + Radial Fog - Soft particles + Sky Blending Start - Weather particle occlusion + Sky Blending - cells + Object Paging Min Size - Shadows + Viewing Distance - bounds + Distant Land - primitives + Active Grid Object Paging - none + Transparent Postpass - <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> + Auto Exposure Speed - Shadow planes computation method + Enable Post Processing - <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + Shadow Planes Computation Method - unit(s) + Enable Actor Shadows - <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + Fade Start Multiplier - Enable actor shadows + Enable Player Shadows - 512 + Shadow Map Resolution - 1024 + Shadow Distance Limit: - 2048 + Enable Object Shadows - 4096 + Enable Indoor Shadows - <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> + Enable Terrain Shadows - Fade start multiplier + Maximum Light Distance - <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + Max Lights - Enable player shadows + Lighting Method - <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> + Bounding Sphere Multiplier - Shadow map resolution + Minimum Interior Brightness - <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> + Audio Device - Shadow distance limit: + HRTF Profile - <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + Tooltip and Crosshair - Enable object shadows + GUI Scaling Factor - <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + Show Effect Duration - Enable indoor shadows + Change Dialogue Topic Color - <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> + Font Size - Enable terrain shadows + Show Projectile Damage - Lighting + Show Melee Info - Lighting method + Stretch Menu Background - Audio device + Show Owned Objects - HRTF profile + Show Enchant Chance - Tooltip + Maximum Quicksaves - Crosshair + Screenshot Format - Tooltip and crosshair + Grab Cursor - Maximum quicksaves + Default Cell - Screenshots + Bounds - Screenshot format + Primitives - <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> + None - <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> + Always Allow Actors to Follow over Water - <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> + Racial Variation in Speed Fix - <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> + Use Navigation Mesh for Pathfinding - <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> + Trainers Choose Offered Skills by Base Value - <html><head/><body><p>Set the internal handling of light sources.</p> -<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> -<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> -<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> + Steal from Knocked out Actors in Combat - Legacy + Factor Strength into Hand-to-Hand Combat - Shaders (compatibility) + Turn to Movement Direction - <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> + Adjust Coverage for Alpha Test - <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + Use the Camera as the Sound Listener - In third-person view, use the camera as the sound listener instead of the player character. + Can Zoom on Maps - Use the camera as the sound listener + Add "Time Played" to Saves - Maximum light distance + Notify on Saved Screenshot - Max lights + Skip Menu and Generate Default Character - Bounding sphere multiplier + Start Default Character at - Minimum interior brightness + Run Script After Startup: diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 52499b7b3c..a02ca403df 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -84,11 +84,11 @@ Отменить - Remove unused tiles + Remove Unused Tiles Удалить неиспользуемые тайлы - Max size + Max Size Максимальный размер @@ -148,16 +148,16 @@ Ctrl+R - <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - <html><head/><body><p>подсказка: файлы данных, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Подсказка: файлы данных, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> - <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - <html><head/><body><p>подсказка: директории, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Подсказка: директории, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> - <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - <html><head/><body><p>подсказка: архивы, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Подсказка: архивы, не включенные в текущий список, <span style=" font-style:italic;font-weight: bold">выделены</span></p></body></html> @@ -227,15 +227,15 @@ Экран - Window mode + Window Mode Режим окна - Framerate limit + Framerate Limit Максимум кадров в секунду - Window border + Window Border Рамка окна @@ -243,11 +243,11 @@ Разрешение экрана - Anti-aliasing + Anti-Aliasing Сглаживание - Vertical synchronization + Vertical Synchronization Вертикальная синхронизация @@ -270,7 +270,7 @@ Импорт настроек из Morrowind - File to import settings from: + File to Import Settings From: Импортировать настройки из файла: @@ -278,8 +278,8 @@ Выбрать... - Import add-on and plugin selection (creates a new Content List) - Импортировать подключенные плагины (создает новый список плагинов) + Import Add-on and Plugin Selection + Импортировать список подключенных аддонов и плагинов Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, @@ -290,7 +290,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Включите эту опцию, если вы все равно хотите использовать оригинальные шрифты вместо шрифтов OpenMW, или если вы используете сторонние растровые шрифты. - Import bitmap fonts setup + Import Bitmap Fonts Импортировать растровые шрифты @@ -609,7 +609,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Делает изменение отношения торговцев к персонажу игрока из-за торговли постоянным.</p></body></html> - Permanent barter disposition changes + Permanent Barter Disposition Changes Постоянная смена отношения торговцев @@ -617,7 +617,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Немедленно вводить спутников персонажа игрока в бой с противниками, начавшими бой с ними или персонажем игрока. Если настройка отключена, они не вступят в бой до первой атаки от противников или игрока.</p></body></html> - Followers defend immediately + Followers Defend Immediately Спутники защищаются сразу @@ -633,7 +633,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Предотвращает вступление в бой персонажей, подвергнутых эффектам Усмирения, каждый кадр - как в Morrowind без MCP.</p></body></html> - Classic Calm spells behavior + Classic Calm Spells Behavior Классическое поведение Усмирения @@ -641,7 +641,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Делает стоимость заполненных камней душ зависимой только от силы души.</p></body></html> - Soulgem values rebalance + Soulgem Values Rebalance Ребаланс стоимости камней душ @@ -649,11 +649,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Делает так, чтобы персонаж плыл немного вверх относительно камеры в режиме от третьего лица. Предназначено для того, чтобы было проще плыть по поверхности воды без погружения в нее.</p></body></html> - Swim upward correction + Swim Upward Correction Коррекция при плавании вверх - Enchanted weapons are magical + Enchanted Weapons Are Magical Зачарованное оружие - магическое @@ -665,7 +665,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Эффекты отраженных заклинаний Поглощения не отзеркаливаются - как в Morrowind.</p></body></html> - Classic reflected Absorb spells behavior + Classic Reflected Absorb Spells Behavior Классическое поведение Поглощения @@ -673,7 +673,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда включено, NPC пытаются избегать столкновения с другими персонажами.</p></body></html> - NPCs avoid collisions + NPCs Avoid Collisions Персонажи избегают столкновения @@ -681,7 +681,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Не учитывать множитель веса расы при вычислении скорости перемещения.</p></body></html> - Racial variation in speed fix + Racial Variation in Speed Fix Фикс влияния веса рас на Скорость @@ -689,7 +689,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда эта настройка включена, игрок может обыскивать тела персонажей (например, призванных существ) во время анимации их смерти, если они не находятся в бою. Если при этом игрок убрал тело, то нам приходится сразу увеличивать счетчик количества погибших и запускать скрипт удаленного персонажа.</p><p>Когда эта настройка отключена, игроку всегда придется ждать завершения анимации смерти. В основном предотвращает эксплойт с призванными существами (сбор дорогого оружия с дремор и золотых святых). Конфликтует с модами на манекены, которые используют команду SkipAnim для предотвращения завершения анимации смерти.</p></body></html> - Can loot during death animation + Can Loot During Death Animation Обыск тел во время анимации смерти @@ -701,7 +701,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Позволяет атакам существ без оружия повреждать броню цели, по аналогии с атаками оружием.</p></body></html> - Unarmed creature attacks damage armor + Unarmed Creature Attacks Damage Armor Атаки существ повреждают броню @@ -709,11 +709,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Отключено - Affect werewolves + Affect Werewolves Включая оборотней - Do not affect werewolves + Do Not Affect Werewolves Не включая оборотней @@ -725,11 +725,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Форма объектов столкновений, используемых для физики и поиска путей с помощью навигационной сетки. Цилиндры дают лучшую совместимость между поиском путей и возможностью двигаться по ним. Изменение значения этой настройки влияет на создание навигационной сетки, поэтому сетка, сгенерированная с одним значением, не может быть использована для другого. - Axis-aligned bounding box + Axis-Aligned Bounding Box Параллелепипед - Rotating box + Rotating Box Вращающийся параллелепипед @@ -749,7 +749,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Использовать анимации сотворения заклинаний для магических предметов, по аналогии с заклинаниями.</p></body></html> - Use magic item animation + Use Magic Item Animation Анимации магических предметов @@ -757,7 +757,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Делает перемещение персонажей более плавным. Рекомендуется использовать совместно с настройкой "Поворот в направлении движения".</p></body></html> - Smooth movement + Smooth Movement Плавное перемещение @@ -765,7 +765,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Загружать KF-файлы и файлы скелетов из директории Animations</p></body></html> - Use additional animation sources + Use Additional Animation Sources Использовать источники анимаций @@ -773,7 +773,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Влияет на перемещение по диагонали и в сторону. Включение этой настройки делает перемещение более реалистичным.</p><p>При ее выключении тело персонажа всегда целиком направлено в направлении движения. Так как в игре нет анимаций для движения по диагонали, из-за этого получается скольжение.</p><p>Когда настройка включена, только ноги персонажа поворачиваются в направлении движения. Верхняя часть тела поворачивается частично. Голова всегда смотрит в одном направлении с камерой. В бою этот режим работает только для движения по диагонали, вне боя он также влияет на движение влево и вправо. Также во время плавания тело персонажа поворачивается в направлении движения.</p></body></html> - Turn to movement direction + Turn to Movement Direction Поворот в направлении движения @@ -781,7 +781,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Отображать зачехленное оружие (с колчанами и ножнами). Требует ресурсы из модов.</p></body></html> - Weapon sheathing + Weapon Sheathing Зачехление оружия @@ -789,7 +789,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Отображать щиты на спине. Требует ресурсы из модов.</p></body></html> - Shield sheathing + Shield Sheathing Зачехление щитов @@ -805,7 +805,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Когда настройка отключена, карты используются, только если они явно заданы в модели (.nif или .osg). Влияет на объекты.</p></body></html> - Auto use object normal maps + Auto Use Object Normal Maps Карты нормалей объектов @@ -813,7 +813,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>См. 'Карты нормалей объектов'. Влияет на ландшафт.</p></body></html> - Auto use terrain normal maps + Auto Use Terrain Normal Maps Карты нормалей ландшафта @@ -829,7 +829,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov (.osg-моделях, они не поддерживаются в .nif-моделях). Влияет на объекты.</p></body></html> - Auto use object specular maps + Auto Use Object Specular Maps Спекулярные карты объектов @@ -837,7 +837,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если существует файл с маской, заданной с помощью настройки 'terrain specular map pattern' то он будет использоваться в качестве спекулярной карты. Он должен представлять собой текстуру, содержащую цвет слоя в RGB-канале и мощность спекулярного освещения в альфа-канале.</p></body></html> - Auto use terrain specular maps + Auto Use Terrain Specular Maps Спекулярные карты ландшафта @@ -851,7 +851,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov </p></body></html> - Bump/reflect map local lighting + Bump/Reflect Map Local Lighting Локальное освещение карт отражений @@ -859,7 +859,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Позволяет MSAA работать с моделями с альфа-тестированием, что позволяет улучшить отображение граней и избежать их пикселизации. Может снизить производительность.</p></body></html> - Use anti-aliased alpha testing + Use Anti-Aliased Alpha Testing Сглаживание альфа-тестирования @@ -871,7 +871,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Эмуляция сохранения покрытия в MIP-картах, чтобы модели с альфа-тестированием не усыхали по мере удаления от камеры. Однако если MIP-карты на уровне самих текстур уже созданы с сохранением покрытия, покрытие будет расти по мере удаления от камеры, так что читайте инструкции к вашим модам.</p></body></html> - Adjust coverage for alpha test + Adjust Coverage for Alpha Test Покрытие альфа-тестирования @@ -889,7 +889,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov С этой настройкой плотность тумана для объекта зависит от расстояния от камеры до объекта (т.н. евклидово расстояние), благодаря чему туман выглядит не так искусственно, особенно при большом поле зрения.</p></body></html> - Radial fog + Radial Fog Радиальный туман @@ -897,7 +897,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Использование экспоненциальной формулы для тумана. По умолчанию используется линейный туман.</p></body></html> - Exponential fog + Exponential Fog Экспоненциальный туман @@ -905,7 +905,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Снизить видимость плоскости отсечения с помощью смешения объектов с небом.</p></body></html> - Sky blending + Sky Blending Смешивать с небом @@ -913,7 +913,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Множитель расстояния обзора, определяющий начало смешения.</p></body></html> - Sky blending start + Sky Blending Start Минимальное расстояние смешения @@ -921,7 +921,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Ландшафт - Viewing distance + Viewing Distance Расстояние обзора @@ -929,7 +929,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Определяет, насколько большим должен быть объект, чтобы отображаться на удаленном ландшафте. Для этого размер объекта делится на его расстояние до камеры, и результат сравнивается со значением этой настройки. Чем меньше значение, тем больше объектов будет отображаться в сцене.</p></body></html> - Object paging min size + Object Paging Min Size Минимальный размер склеиваемых объектов @@ -937,7 +937,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда включено, на удаленном ландшафте будут отображаться объекты, склеенные вместе с помощью специального алгоритма. Когда выключено, будет отображаться только сам ландшафт.</p></body></html> - Distant land + Distant Land Удаленный ландшафт @@ -945,7 +945,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Склеивать объекты в активных ячейках.</p></body></html> - Active grid object paging + Active Grid Object Paging Склеивание в активных ячейках @@ -953,7 +953,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, модели с поддержкой переключения в зависимости от времени суток будут ее использовать.</p></body></html> - Day night switch nodes + Day Night Switch Nodes Переключение узлов дня и ночи @@ -965,7 +965,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, то будет включена постобработка графики.</p></body></html> - Enable post processing + Enable Post Processing Включить постобработку @@ -973,7 +973,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Отрисовывать прозрачные объекты заново - со строгой границей прозрачности.</p></body></html> - Transparent postpass + Transparent Postpass Дополнительный проход для прозрачных объектов @@ -981,7 +981,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Определяет, насколько быстро может меняться эффект аккомодации глаза от кадра к кадру. Более низкие значения означают более медленные преобразования.</p></body></html> - Auto exposure speed + Auto Exposure Speed Скорость автоматической экспозиции @@ -1025,7 +1025,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Эта настройка отвечает за масштабирование окон внутриигрового интерфейса. Значение 1.0 соответствует нормальному размеру интерфейса.</p></body></html> - GUI scaling factor + GUI Scaling Factor Масштаб интерфейса @@ -1033,7 +1033,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда настройка включена, в подсказке, всплывающей при наведении курсора на иконку магического эффекта, будет отображаться оставшаяся длительность магического эффекта или источника света.</p><p>По умолчанию настройка отключена.</p></body></html> - Show effect duration + Show Effect Duration Показывать длительность эффектов @@ -1041,7 +1041,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, у тем диалогов будет другой цвет, если у вашего собеседника есть уникальная реплика по заданной теме, или же если вы уже видели текст темы. Цвета могут быть настроены через settings.cfg.</p><p>По умолчанию настройка отключена.</p></body></html> - Change dialogue topic color + Change Dialogue Topic Color Смена цвета тем для диалогов @@ -1049,7 +1049,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Размер символов в текстах. - Font size + Font Size Размер шрифтов @@ -1057,7 +1057,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить возможность масштабирования на локальной и глобальной картах.</p></body></html> - Can zoom on maps + Can Zoom on Maps Включить масштабирование карты @@ -1069,7 +1069,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках стрел и болтов будет показан их бонус к урону.</p><p>По умолчанию настройка выключена.</p></body></html> - Show projectile damage + Show Projectile Damage Показывать урон снарядов @@ -1077,7 +1077,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если эта настройка включена, на всплывающих подсказках оружия ближнего боя будут показаны его дальность и скорость атаки.</p><p>По умолчанию настройка выключена.</p></body></html> - Show melee info + Show Melee Info Показывать информацию об оружии @@ -1085,11 +1085,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Растягивать фон меню, экранов загрузки и т.д., чтобы изображение соответствовало соотношению сторон выбранного разрешения экрана.</p></body></html> - Stretch menu background + Stretch Menu Background Растягивать фон меню - Show owned objects + Show Owned Objects Выделять объекты с владельцами @@ -1097,7 +1097,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Показывать шанс успеха в меню зачарования, или же нет.</p><p>По умолчанию настройка выключена.</p></body></html> - Show enchant chance + Show Enchant Chance Показывать шанс успеха зачарования @@ -1105,7 +1105,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Предотвращает экипировку торговцами предметов, которые им проданы.</p></body></html> - Merchant equipping fix + Merchant Equipping Fix Фикс экипировки предметов торговцами @@ -1125,7 +1125,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Эта настройка определяет, будет ли отображаться время с начала новой игры для выбранного сохранения в меню загрузки.</p></body></html> - Add "Time Played" to saves + Add "Time Played" to Saves Выводить "Время в игре" в сохранениях @@ -1141,7 +1141,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov TGA - Notify on saved screenshot + Notify on Saved Screenshot Уведомление при сохранении снимка @@ -1157,23 +1157,23 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Когда эта настройка включена, OpenMW будет управлять курсором мыши.</p><p>В “режиме обзора”, OpenMW будет захватывать курсор в центре экрана вне зависимости от значения этой настройки (потому что курсор всегда расположен по центру в окне OpenMW). Однако в режиме меню эта настройка определяет поведение выхода курсора за пределы окна OpenMW. Если настройка включена, курсор остановится на краю окна, предотвращая доступ к другим приложениям. Если выключена, курсор может свободно перемещаться по рабочему столу.</p><p>Эта настройка не применяется к экрану, на котором нажата клавиша “Escape” (там курсор никогда не захватывается). Вне зависимости от значения этой настройки “Alt-Tab” и некоторые другие зависимые от операционной системы комбинации клавиш могут быть использованы, чтобы вернуть управление курсором операционной системе. Эта настройка также взаимодействует с настройкой "minimize on focus loss", определяя, что именно считать потерей фокуса. На системах с двумя экранами может быть проще получить доступ ко второму экрану, если настройка выключена.</p><p>Замечание для разработчиков: лучше запускать игру с отключенной настройкой при запуске игры через отладчик, чтобы курсор не становился недоступен, когда игра останавливается на точке останова.</p></body></html> - Grab cursor + Grab Cursor Захватывать курсор - Skip menu and generate default character + Skip Menu and Generate Default Character Пропустить меню и создать персонажа по умолчанию - Start default character at + Start Default Character at Запустить с персонажем по умолчанию в локации - default cell + Default Cell по умолчанию - Run script after startup: + Run Script After Startup: Запустить скрипт после запуска: @@ -1185,7 +1185,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>Когда настройка включена, в фоне создаются навигационные меши для геометрии игрового мира. Когда она отключена, то используются только путевые точки из игровых файлов. На однопоточных системах включенный навигатор может значительно снизить скорость смены локаций. На многопоточных системах влияние на производительность незначительное. Также на многопоточных системах задержки могут зависеть от других настроек или производительности системы. Передвижение по открытому миру, а также вход в локации и выход из них могут привести к обновлению кэша. Персонажи могут не иметь возможности найти путь вокруг них, пока обновление не будет завершено. Можете попробовать отключить навигатор, если вы предпочитаете старомодный ИИ, который не знает, как до вас добраться, пока вы стоите за камнем и колдуете огненные стрелы.</p></body></html> - Use navigation mesh for pathfinding + Use Navigation Mesh for Pathfinding Использовать навигационную сетку @@ -1193,7 +1193,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>В режиме вида от третьего лица камера перемещается согласно анимациям движения персонажа игрока. Включение этой настройки отключает это поведение, убирая зависимость движения персонажа игрока от состояния его анимаций. Такое поведение было в OpenMW версии 0.48 и ранее.</p></body></html> - Player movement ignores animation + Player Movement Ignores Animation Движение игрока обходит анимации @@ -1201,7 +1201,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Игровой процесс - Always allow actors to follow over water + Always Allow Actors to Follow over Water Позволить всем следовать по воде @@ -1209,11 +1209,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если настройка включена, требуются магические метательные снаряды, чтобы обойти сопротивление обычному оружию или уязвимость к нему. Если отключена, то требуются магические снаряды или магическое оружие дальнего боя.</p></body></html> - Only magical ammo bypass resistance + Only Magical Ammo Bypass Resistance Только снаряды обходят сопротивление - Graphic herbalism + Graphic Herbalism Графический гербализм @@ -1221,31 +1221,31 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Разрешает зачарованному оружию без флага Магическое обходить сопротивление обычному оружию, как в Morrowind.</p></body></html> - Trainers choose offered skills by base value + Trainers Choose Offered Skills by Base Value Учителя выбирают базовые навыки - Steal from knocked out actors in combat + Steal from Knocked out Actors in Combat Кража у персонажей без сознания в бою - Factor strength into hand-to-hand combat + Factor Strength into Hand-to-Hand Combat Учет Силы в рукопашном бою - Background physics threads + Background Physics Threads Количество фоновых потоков для физики - Actor collision shape type + Actor Collision Shape Type Форма объекта столкновений для персонажей - Soft particles + Soft Particles Мягкие частицы - Weather particle occlusion + Weather Particle Occlusion Окклюзия погодных частиц @@ -1257,23 +1257,23 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Тени - bounds - по границам объектов + Bounds + По границам объектов - primitives - по примитивам + Primitives + По примитивам - none - отключено + None + Отключено <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> <html><head/><body><p>Определяет используемый тип вычисления границ сцены. Вычисление по границам объектов (по умолчанию) дает хороший баланс между производительностью и качеством теней, вычисление по примитивам улучшает качество теней, а еще можно полностью отключить вычисление.</p></body></html> - Shadow planes computation method + Shadow Planes Computation Method Метод определения границ для теней @@ -1289,7 +1289,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить тени от NPC и существ, кроме персонажа игрока. Может незначительно снизить производительность.</p></body></html> - Enable actor shadows + Enable Actor Shadows Включить тени от персонажей @@ -1313,7 +1313,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Множитель от расстояния выше, при котором тени начнут плавно угасать.</p></body></html> - Fade start multiplier + Fade Start Multiplier Множитель начала угасания теней @@ -1321,7 +1321,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить тени от персонажа игрока. Может очень незначительно снизить производительность.</p></body></html> - Enable player shadows + Enable Player Shadows Включить тени от персонажа игрока @@ -1329,7 +1329,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Разрешение отдельных теневых карт. Увеличение значения улучшает качество теней, но может незначительно снизить производительность.</p></body></html> - Shadow map resolution + Shadow Map Resolution Разрешение теневой карты @@ -1337,7 +1337,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Расстояние от камеры, при достижении которого тени полностью исчезают.</p></body></html> - Shadow distance limit: + Shadow Distance Limit: Максимальная дистанция теней: @@ -1345,7 +1345,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить тени от объектов. Может значительно снизить производительность.</p></body></html> - Enable object shadows + Enable Object Shadows Включить тени от объектов @@ -1353,7 +1353,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Из-за ограничений в файлах Morrowind, только персонажи могут отбрасывать тени в интерьерах, что может раздражать.</p><p>Не имеет эффекта, если тени от персонажей отключены.</p></body></html> - Enable indoor shadows + Enable Indoor Shadows Включить тени в интерьерах @@ -1361,7 +1361,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Включить тени от ландшафта (включая удаленный ландшафт). Может значительно снизить производительность и качество теней.</p></body></html> - Enable terrain shadows + Enable Terrain Shadows Включить тени от ландшафта @@ -1369,15 +1369,15 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Освещение - Lighting method + Lighting Method Способ освещения - Audio device + Audio Device Звуковое устройство - HRTF profile + HRTF Profile Профиль HRTF @@ -1389,11 +1389,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Прицел - Tooltip and crosshair + Tooltip and Crosshair Всплывающая подсказка и прицел - Maximum quicksaves + Maximum Quicksaves Количество быстрых сохранений @@ -1401,7 +1401,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Снимки экрана - Screenshot format + Screenshot Format Формат снимков экрана @@ -1409,7 +1409,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Максимальное расстояние, на котором будут отображаться источники света (во внутриигровых единицах измерения).</p><p>Если 0, то расстояние не ограничено.</p></body></html> - Maximum light distance + Maximum Light Distance Дальность отображения источников света @@ -1417,7 +1417,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Максимальное количество источников света для каждого объекта.</p><p>Низкие числа (близкие к значению по умолчанию) приводят к резким перепадам освещения, как при устаревшем методе освещения.</p></body></html> - Max lights + Max Lights Макс. кол-во источников света @@ -1447,7 +1447,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Множитель размера ограничивающей сферы источников света.</p><p>Высокие значения делают затухание света плавнее, но требуют более высокого максимального количества источников света.</p><p>Настройка не влияет на уровень освещения или мощность источников света.</p></body></html> - Bounding sphere multiplier + Bounding Sphere Multiplier Множитель размера ограничивающей сферы @@ -1459,11 +1459,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Использовать в виде от третьего лица положение камеры, а не персонажа игрока для прослушивания звуков. - Use the camera as the sound listener + Use the Camera as the Sound Listener Использовать камеру как слушателя - Minimum interior brightness + Minimum Interior Brightness Минимальный уровень освещения в помещениях diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts index 5749cf2d5d..33c7a5fb53 100644 --- a/files/lang/wizard_de.ts +++ b/files/lang/wizard_de.ts @@ -81,15 +81,15 @@ - Import settings from Morrowind.ini + Import Settings From Morrowind.ini - Import add-on and plugin selection + Import Add-on and Plugin Selection - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index 9b5acbc9e9..ab0b01af73 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -81,15 +81,15 @@ - Import settings from Morrowind.ini + Import Settings From Morrowind.ini - Import add-on and plugin selection + Import Add-on and Plugin Selection - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts index 0a2e6e5561..5784b11eac 100644 --- a/files/lang/wizard_ru.ts +++ b/files/lang/wizard_ru.ts @@ -81,15 +81,15 @@ <html><head/><body><p>Чтобы OpenMW мог работать правильно, ему нужно импортировать настройки из файла с настройками Morrowind.</p><p><span style=" font-weight:bold;">Подсказка:</span> Также можно импортировать настройки позже, запустив Мастер импорта заново.</p><p/></body></html> - Import settings from Morrowind.ini + Import Settings From Morrowind.ini Импортировать настройки из Morrowind.ini - Import add-on and plugin selection - Импортировать список подключенных плагинов + Import Add-on and Plugin Selection + Импортировать список подключенных аддонов и плагинов - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini Импортировать растровые шрифты из Morrowind.ini From c4857260460b7924f27ed9523c7cb1d94e5d7a9d Mon Sep 17 00:00:00 2001 From: Joakim Berg Date: Mon, 8 Apr 2024 09:11:40 +0000 Subject: [PATCH 364/451] Update Swedish translations --- files/data/l10n/OMWEngine/sv.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/files/data/l10n/OMWEngine/sv.yaml b/files/data/l10n/OMWEngine/sv.yaml index 15a9afa495..791efb660d 100644 --- a/files/data/l10n/OMWEngine/sv.yaml +++ b/files/data/l10n/OMWEngine/sv.yaml @@ -110,9 +110,9 @@ LightsBoundingSphereMultiplierTooltip: "Förvalt: 1.65\nMultiplikator för ljuse LightsFadeStartMultiplier: "Blekningsstartmultiplikator" LightsFadeStartMultiplierTooltip: "Förvalt: 0.85\nFraktion av det maximala avståndet från vilket ljuskällor börjar blekna.\n\nVälj lågt värde för långsammare övergång eller högre värde för snabbare övergång." LightsLightingMethodTooltip: "Välj intern hantering av ljuskällor.\n\n -# \"Legacy\" använder alltid max 8 ljuskällor per objekt och ger ljussättning lik ett gammaldags spel.\n\n -# \"Shader (compatibility)\" tar bort begränsningen med max 8 ljuskällor per objekt. Detta läge aktiverar också ljus på marktäckning och ett konfigurerbart ljusbleknande. Rekommenderas för äldre hårdvara tillsammans med en ljusbegränsning nära 8.\n\n -# \"Shaders\" har alla fördelar som \"Shaders (compatibility)\" har, med med ett modernt förhållningssätt som möjliggör fler maximalt antal ljuskällor med liten eller ingen prestandaförlust på modern hårdvara." + \"Gammaldags\" använder alltid max 8 ljuskällor per objekt och ger ljussättning likt ett gammaldags spel.\n\n + \"Shader (kompatibilitet)\" tar bort begränsningen med max 8 ljuskällor per objekt. Detta läge aktiverar också ljus på marktäckning och ett konfigurerbart ljusbleknande. Rekommenderas för äldre hårdvara tillsammans med en ljusbegränsning nära 8.\n\n + \"Shader\" har alla fördelar som \"Shader (kompatibilitet)\" har, med med ett modernt förhållningssätt som möjliggör fler maximalt antal ljuskällor med liten eller ingen prestandaförlust på modern hårdvara." LightsMaximumDistance: "Maximalt ljusavstånd" LightsMaximumDistanceTooltip: "Förvalt: 8192\nMaximala avståndet där ljuskällor syns (mätt i enheter).\n\nVärdet 0 ger oändligt avstånd." LightsMinimumInteriorBrightness: "Minsta ljusstyrka i interiörer" @@ -154,6 +154,7 @@ SensitivityHigh: "Hög" SensitivityLow: "Låg" SettingsWindow: "Inställningar" Subtitles: "Undertexter" +SunlightScattering: "Solljusspridning" TestingExteriorCells: "Testar exteriöra celler" TestingInteriorCells: "Testar interiöra celler" TextureFiltering: "Texturfiltrering" @@ -166,8 +167,9 @@ TogglePostProcessorHUD: "Visa/dölj Postprocess-HUD" TransparencyFull: "Full" TransparencyNone: "Ingen" Video: "Bild" -VSync: "VSynk" ViewDistance: "Siktavstånd" +VSync: "VSynk" +VSyncAdaptive: "Adaptiv" Water: "Vatten" WaterShader: "Vattenshader" WaterShaderTextureQuality: "Texturkvalitet" @@ -177,3 +179,4 @@ WindowModeFullscreen: "Fullskärm" WindowModeHint: "Notera: Fullskärm i fönsterläge\nanvänder alltid skärmens nativa upplösning." WindowModeWindowed: "Fönster" WindowModeWindowedFullscreen: "Fullskärm i fönsterläge" +WobblyShores: "Vaggande stränder" From 0d547c54385c6eb7750e449aef6dc3fa812a863b Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 8 Apr 2024 13:37:36 +0100 Subject: [PATCH 365/451] Resolve merge conflicts from https://gitlab.com/OpenMW/openmw/-/merge_requests/3893 --- apps/wizard/ui/importpage.ui | 6 +++--- files/lang/wizard_de.ts | 6 +++--- files/lang/wizard_fr.ts | 6 +++--- files/lang/wizard_ru.ts | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/wizard/ui/importpage.ui b/apps/wizard/ui/importpage.ui index acc0d268ec..669920c5f3 100644 --- a/apps/wizard/ui/importpage.ui +++ b/apps/wizard/ui/importpage.ui @@ -33,7 +33,7 @@ - Import settings from Morrowind.ini + Import Settings From Morrowind.ini true @@ -43,7 +43,7 @@ - Import add-on and plugin selection + Import Add-on and Plugin Selection true @@ -53,7 +53,7 @@ - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts index 5749cf2d5d..33c7a5fb53 100644 --- a/files/lang/wizard_de.ts +++ b/files/lang/wizard_de.ts @@ -81,15 +81,15 @@ - Import settings from Morrowind.ini + Import Settings From Morrowind.ini - Import add-on and plugin selection + Import Add-on and Plugin Selection - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index 9b5acbc9e9..ab0b01af73 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -81,15 +81,15 @@ - Import settings from Morrowind.ini + Import Settings From Morrowind.ini - Import add-on and plugin selection + Import Add-on and Plugin Selection - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts index 0a2e6e5561..e26735443e 100644 --- a/files/lang/wizard_ru.ts +++ b/files/lang/wizard_ru.ts @@ -81,15 +81,15 @@ <html><head/><body><p>Чтобы OpenMW мог работать правильно, ему нужно импортировать настройки из файла с настройками Morrowind.</p><p><span style=" font-weight:bold;">Подсказка:</span> Также можно импортировать настройки позже, запустив Мастер импорта заново.</p><p/></body></html> - Import settings from Morrowind.ini + Import Settings From Morrowind.ini Импортировать настройки из Morrowind.ini - Import add-on and plugin selection + Import Add-on and Plugin Selection Импортировать список подключенных плагинов - Import bitmap fonts setup from Morrowind.ini + Import Bitmap Fonts Setup From Morrowind.ini Импортировать растровые шрифты из Morrowind.ini From a179e9c001c36ac8f7dbe4915c7ee6e02d80e466 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 8 Apr 2024 13:43:42 +0100 Subject: [PATCH 366/451] The rest of the merge conflict I didn't notice it as GitLab didn't highlight the diff properly. --- files/lang/wizard_ru.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts index e26735443e..5784b11eac 100644 --- a/files/lang/wizard_ru.ts +++ b/files/lang/wizard_ru.ts @@ -86,7 +86,7 @@ Import Add-on and Plugin Selection - Импортировать список подключенных плагинов + Импортировать список подключенных аддонов и плагинов Import Bitmap Fonts Setup From Morrowind.ini From 1ed82662ee3de7f4cef3568bbb6cecec8ef29e73 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 8 Apr 2024 17:54:13 +0200 Subject: [PATCH 367/451] Don't show nameless higher ranks --- CHANGELOG.md | 1 + apps/openmw/mwgui/statswindow.cpp | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 587e5e7a52..03b5769c70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -223,6 +223,7 @@ Feature #7860: Lua: Expose NPC AI settings (fight, alarm, flee) Feature #7875: Disable MyGUI windows snapping Feature #7914: Do not allow to move GUI windows out of screen + Feature #7923: Don't show non-existent higher ranks for factions with fewer than 9 ranks Task #5896: Do not use deprecated MyGUI properties Task #6085: Replace boost::filesystem with std::filesystem Task #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 6e7d2c2ba2..69f0b4b449 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -582,9 +582,8 @@ namespace MWGui const std::set& expelled = PCstats.getExpelled(); bool firstFaction = true; - for (auto& factionPair : mFactions) + for (const auto& [factionId, factionRank] : mFactions) { - const ESM::RefId& factionId = factionPair.first; const ESM::Faction* faction = store.get().find(factionId); if (faction->mData.mIsHidden == 1) continue; @@ -611,10 +610,10 @@ namespace MWGui text += "\n#{fontcolourhtml=normal}#{sExpelled}"; else { - const int rank = std::clamp(factionPair.second, 0, 9); - text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank]; - - if (rank < 9) + const auto rank = static_cast(std::max(0, factionRank)); + if (rank < faction->mRanks.size()) + text += std::string("\n#{fontcolourhtml=normal}") + faction->mRanks[rank]; + if (rank + 1 < faction->mRanks.size() && !faction->mRanks[rank + 1].empty()) { // player doesn't have max rank yet text += std::string("\n\n#{fontcolourhtml=header}#{sNextRank} ") + faction->mRanks[rank + 1]; From 2f6acbd7da1c5ee026209214a9c1fda76f348a52 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 10 Apr 2024 21:16:22 +0200 Subject: [PATCH 368/451] Deregister the player before loading a new one --- apps/openmw/mwworld/player.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index b498bb488b..7849ba1458 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -325,6 +325,7 @@ namespace MWWorld player.mObject.mEnabled = true; } + MWBase::Environment::get().getWorldModel()->deregisterLiveCellRef(mPlayer); mPlayer.load(player.mObject); for (size_t i = 0; i < mSaveAttributes.size(); ++i) From 8751203849708d92605faac2d96dd354a0b62e9e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 10 Apr 2024 22:23:41 +0300 Subject: [PATCH 369/451] Don't run target-specific spell infliction code when there's no target (#7926) --- apps/openmw/mwmechanics/spellcasting.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 2fd250f8c1..a7e27c9ddd 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -188,6 +188,9 @@ namespace MWMechanics for (auto& enam : effects.mList) { + if (target.isEmpty()) + break; + if (enam.mData.mRange != range) continue; const ESM::MagicEffect* magicEffect = store.find(enam.mData.mEffectID); From 8c2c66d59e033c6ae8467ad04f6f791d9721d38d Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 11 Apr 2024 02:16:06 +0100 Subject: [PATCH 370/451] .nif check, matrix mult feedback, auto usage, reuse NodeMap typedef --- apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/rotatecontroller.cpp | 6 +++--- components/resource/scenemanager.cpp | 4 +--- components/sceneutil/visitor.hpp | 12 ++++-------- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 82081658a6..31b5d36c79 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1491,7 +1491,7 @@ namespace MWRender // osgAnimation formats with skeletons should have their nodemap be bone instances // FIXME: better way to detect osgAnimation here instead of relying on extension? - mRequiresBoneMap = mSkeleton != nullptr && !Misc::StringUtils::ciEndsWith(model, "nif"); + mRequiresBoneMap = mSkeleton != nullptr && !Misc::StringUtils::ciEndsWith(model, ".nif"); if (previousStateset) mObjectRoot->setStateSet(previousStateset); diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index 47b271c40d..61a2a2628a 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -50,9 +50,9 @@ namespace MWRender { osgAnimation::Bone* parent = b->getBoneParent(); if (parent) - b->setMatrixInSkeletonSpace(matrix * parent->getMatrixInSkeletonSpace()); - else - b->setMatrixInSkeletonSpace(matrix); + matrix *= parent->getMatrixInSkeletonSpace(); + + b->setMatrixInSkeletonSpace(matrix); } traverse(node, nv); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index bf2674a98f..4bdd620819 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -399,9 +399,7 @@ namespace Resource osg::Callback* cb = node.getUpdateCallback(); while (cb) { - osgAnimation::AnimationUpdateCallback* animCb - = dynamic_cast*>(cb); - + auto animCb = dynamic_cast*>(cb); if (animCb) animCb->setName(Misc::StringUtils::underscoresToSpaces(animCb->getName())); diff --git a/components/sceneutil/visitor.hpp b/components/sceneutil/visitor.hpp index a5af88d7de..a9a943423c 100644 --- a/components/sceneutil/visitor.hpp +++ b/components/sceneutil/visitor.hpp @@ -50,14 +50,14 @@ namespace SceneUtil std::vector mFoundNodes; }; + typedef std::unordered_map, Misc::StringUtils::CiHash, + Misc::StringUtils::CiEqual> + NodeMap; + /// Maps names to nodes class NodeMapVisitor : public osg::NodeVisitor { public: - typedef std::unordered_map, Misc::StringUtils::CiHash, - Misc::StringUtils::CiEqual> - NodeMap; - NodeMapVisitor(NodeMap& map) : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) , mMap(map) @@ -74,10 +74,6 @@ namespace SceneUtil class NodeMapVisitorBoneOnly : public osg::NodeVisitor { public: - typedef std::unordered_map, Misc::StringUtils::CiHash, - Misc::StringUtils::CiEqual> - NodeMap; - NodeMapVisitorBoneOnly(NodeMap& map) : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) , mMap(map) From c3420ed306ddc2a9c369dc4e0903e5e566f49e5f Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 11 Apr 2024 03:01:00 +0100 Subject: [PATCH 371/451] Fix build --- apps/opencs/view/render/actor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/actor.hpp b/apps/opencs/view/render/actor.hpp index a9cc34b00d..09d896e7e7 100644 --- a/apps/opencs/view/render/actor.hpp +++ b/apps/opencs/view/render/actor.hpp @@ -62,7 +62,7 @@ namespace CSVRender osg::ref_ptr mBaseNode; SceneUtil::Skeleton* mSkeleton; - SceneUtil::NodeMapVisitor::NodeMap mNodeMap; + SceneUtil::NodeMap mNodeMap; }; } From fb4edda45dde6cf3ee57e6bf4b28718bb1e91251 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 4 Apr 2024 19:45:29 +0200 Subject: [PATCH 372/451] Validate INFO filters when loading the record --- apps/openmw/mwdialogue/filter.cpp | 19 +++---- apps/openmw/mwdialogue/selectwrapper.cpp | 12 +++- apps/openmw/mwdialogue/selectwrapper.hpp | 4 ++ components/esm3/loadinfo.cpp | 71 +++++++++++++++++++++++- 4 files changed, 94 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 8b67ea28b3..2693811357 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -30,7 +30,7 @@ namespace { bool matchesStaticFilters(const MWDialogue::SelectWrapper& select, const MWWorld::Ptr& actor) { - const ESM::RefId selectId = ESM::RefId::stringRefId(select.getName()); + const ESM::RefId selectId = select.getId(); if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotId) return actor.getCellRef().getRefId() != selectId; if (actor.getClass().isNpc()) @@ -356,19 +356,18 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons { case SelectWrapper::Function_Journal: - return MWBase::Environment::get().getJournal()->getJournalIndex(ESM::RefId::stringRefId(select.getName())); + return MWBase::Environment::get().getJournal()->getJournalIndex(select.getId()); case SelectWrapper::Function_Item: { MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); - return store.count(ESM::RefId::stringRefId(select.getName())); + return store.count(select.getId()); } case SelectWrapper::Function_Dead: - return MWBase::Environment::get().getMechanicsManager()->countDeaths( - ESM::RefId::stringRefId(select.getName())); + return MWBase::Environment::get().getMechanicsManager()->countDeaths(select.getId()); case SelectWrapper::Function_Choice: @@ -546,24 +545,24 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con case SelectWrapper::Function_NotId: - return !(mActor.getCellRef().getRefId() == ESM::RefId::stringRefId(select.getName())); + return mActor.getCellRef().getRefId() != select.getId(); case SelectWrapper::Function_NotFaction: - return !(mActor.getClass().getPrimaryFaction(mActor) == ESM::RefId::stringRefId(select.getName())); + return mActor.getClass().getPrimaryFaction(mActor) != select.getId(); case SelectWrapper::Function_NotClass: - return !(mActor.get()->mBase->mClass == ESM::RefId::stringRefId(select.getName())); + return mActor.get()->mBase->mClass != select.getId(); case SelectWrapper::Function_NotRace: - return !(mActor.get()->mBase->mRace == ESM::RefId::stringRefId(select.getName())); + return mActor.get()->mBase->mRace != select.getId(); case SelectWrapper::Function_NotCell: { std::string_view actorCell = MWBase::Environment::get().getWorld()->getCellName(mActor.getCell()); - return !Misc::StringUtils::ciStartsWith(actorCell, select.getName()); + return !Misc::StringUtils::ciStartsWith(actorCell, select.getCellName()); } case SelectWrapper::Function_SameGender: diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index 94f7f73097..0cee8bb009 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -454,5 +454,15 @@ bool MWDialogue::SelectWrapper::selectCompare(bool value) const std::string MWDialogue::SelectWrapper::getName() const { - return Misc::StringUtils::lowerCase(std::string_view(mSelect.mSelectRule).substr(5)); + return Misc::StringUtils::lowerCase(getCellName()); +} + +std::string_view MWDialogue::SelectWrapper::getCellName() const +{ + return std::string_view(mSelect.mSelectRule).substr(5); +} + +ESM::RefId MWDialogue::SelectWrapper::getId() const +{ + return ESM::RefId::stringRefId(getCellName()); } diff --git a/apps/openmw/mwdialogue/selectwrapper.hpp b/apps/openmw/mwdialogue/selectwrapper.hpp index 0d376d957c..f736b504d8 100644 --- a/apps/openmw/mwdialogue/selectwrapper.hpp +++ b/apps/openmw/mwdialogue/selectwrapper.hpp @@ -95,6 +95,10 @@ namespace MWDialogue std::string getName() const; ///< Return case-smashed name. + + std::string_view getCellName() const; + + ESM::RefId getId() const; }; } diff --git a/components/esm3/loadinfo.cpp b/components/esm3/loadinfo.cpp index 714d59fef4..b091edd3f6 100644 --- a/components/esm3/loadinfo.cpp +++ b/components/esm3/loadinfo.cpp @@ -3,7 +3,65 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include #include +#include + +namespace +{ + enum class SelectRuleStatus + { + Valid, + Invalid, + Ignorable + }; + + SelectRuleStatus isValidSelectRule(std::string_view rule) + { + if (rule.size() < 5) + return SelectRuleStatus::Invalid; + if (rule[4] < '0' || rule[4] > '5') // Comparison operators + return SelectRuleStatus::Invalid; + if (rule[1] == '1') // Function + { + int function = Misc::StringUtils::toNumeric(rule.substr(2, 2), -1); + if (function >= 0 && function <= 73) + return SelectRuleStatus::Valid; + return SelectRuleStatus::Invalid; + } + if (rule.size() == 5) // Missing ID + return SelectRuleStatus::Invalid; + if (rule[3] != 'X') + return SelectRuleStatus::Ignorable; + constexpr auto ignorable + = [](bool valid) { return valid ? SelectRuleStatus::Valid : SelectRuleStatus::Ignorable; }; + switch (rule[1]) + { + case '2': + case '3': + case 'C': + return ignorable(rule[2] == 's' || rule[2] == 'l' || rule[2] == 'f'); + case '4': + return ignorable(rule[2] == 'J'); + case '5': + return ignorable(rule[2] == 'I'); + case '6': + return ignorable(rule[2] == 'D'); + case '7': + return ignorable(rule[2] == 'X'); + case '8': + return ignorable(rule[2] == 'F'); + case '9': + return ignorable(rule[2] == 'C'); + case 'A': + return ignorable(rule[2] == 'R'); + case 'B': + return ignorable(rule[2] == 'L'); + default: + return SelectRuleStatus::Invalid; + } + } +} namespace ESM { @@ -69,7 +127,18 @@ namespace ESM SelectStruct ss; ss.mSelectRule = esm.getHString(); ss.mValue.read(esm, Variant::Format_Info); - mSelects.push_back(ss); + auto valid = isValidSelectRule(ss.mSelectRule); + if (ss.mValue.getType() != VT_Int && ss.mValue.getType() != VT_Float) + valid = SelectRuleStatus::Invalid; + if (valid == SelectRuleStatus::Invalid) + Log(Debug::Warning) << "Skipping invalid SCVR for INFO " << mId; + else + { + mSelects.push_back(ss); + if (valid == SelectRuleStatus::Ignorable) + Log(Debug::Info) + << "Found malformed SCVR for INFO " << mId << " at index " << ss.mSelectRule[0]; + } break; } case fourCC("BNAM"): From cb92d34ddda7c8b10613ae9a09d3817d1659d250 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 11 Apr 2024 17:04:55 +0200 Subject: [PATCH 373/451] Reorder RefData members to decrease its size --- apps/openmw/mwworld/refdata.cpp | 30 +++++++++++++++--------------- apps/openmw/mwworld/refdata.hpp | 14 +++++--------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index f7ba76da21..dc49ff0a4e 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -58,12 +58,12 @@ namespace MWWorld RefData::RefData() : mBaseNode(nullptr) + , mCustomData(nullptr) + , mFlags(0) , mDeletedByContentFile(false) , mEnabled(true) , mPhysicsPostponed(false) - , mCustomData(nullptr) , mChanged(false) - , mFlags(0) { for (int i = 0; i < 3; ++i) { @@ -74,50 +74,50 @@ namespace MWWorld RefData::RefData(const ESM::CellRef& cellRef) : mBaseNode(nullptr) + , mPosition(cellRef.mPos) + , mCustomData(nullptr) + , mFlags(0) // Loading from ESM/ESP files -> assume unchanged , mDeletedByContentFile(false) , mEnabled(true) , mPhysicsPostponed(false) - , mPosition(cellRef.mPos) - , mCustomData(nullptr) , mChanged(false) - , mFlags(0) // Loading from ESM/ESP files -> assume unchanged { } RefData::RefData(const ESM4::Reference& ref) : mBaseNode(nullptr) + , mPosition(ref.mPos) + , mCustomData(nullptr) + , mFlags(0) , mDeletedByContentFile(ref.mFlags & ESM4::Rec_Deleted) , mEnabled(!(ref.mFlags & ESM4::Rec_Disabled)) , mPhysicsPostponed(false) - , mPosition(ref.mPos) - , mCustomData(nullptr) , mChanged(false) - , mFlags(0) { } RefData::RefData(const ESM4::ActorCharacter& ref) : mBaseNode(nullptr) + , mPosition(ref.mPos) + , mCustomData(nullptr) + , mFlags(0) , mDeletedByContentFile(ref.mFlags & ESM4::Rec_Deleted) , mEnabled(!(ref.mFlags & ESM4::Rec_Disabled)) , mPhysicsPostponed(false) - , mPosition(ref.mPos) - , mCustomData(nullptr) , mChanged(false) - , mFlags(0) { } RefData::RefData(const ESM::ObjectState& objectState, bool deletedByContentFile) : mBaseNode(nullptr) - , mDeletedByContentFile(deletedByContentFile) - , mEnabled(objectState.mEnabled != 0) - , mPhysicsPostponed(false) , mPosition(objectState.mPosition) , mAnimationState(objectState.mAnimationState) , mCustomData(nullptr) - , mChanged(true) , mFlags(objectState.mFlags) // Loading from a savegame -> assume changed + , mDeletedByContentFile(deletedByContentFile) + , mEnabled(objectState.mEnabled != 0) + , mPhysicsPostponed(false) + , mChanged(true) { // "Note that the ActivationFlag_UseEnabled is saved to the reference, // which will result in permanently suppressed activation if the reference script is removed. diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 1b57971f11..e0b62c94b6 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -47,6 +47,10 @@ namespace MWWorld MWScript::Locals mLocals; std::shared_ptr mLuaScripts; + ESM::Position mPosition; + ESM::AnimationState mAnimationState; + std::unique_ptr mCustomData; + unsigned int mFlags; /// separate delete flag used for deletion by a content file /// @note not stored in the save game file. @@ -58,20 +62,12 @@ namespace MWWorld bool mPhysicsPostponed : 1; private: - ESM::Position mPosition; - - ESM::AnimationState mAnimationState; - - std::unique_ptr mCustomData; + bool mChanged : 1; void copy(const RefData& refData); void cleanup(); - bool mChanged; - - unsigned int mFlags; - public: RefData(); From a4625ea784d25e7408f480a4ceda4ae21e0bc54e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 11 Apr 2024 22:29:33 +0200 Subject: [PATCH 374/451] Deduplicate dialogue filter parsing --- apps/esmtool/labels.cpp | 11 +- apps/esmtool/record.cpp | 128 ++- apps/opencs/model/tools/topicinfocheck.cpp | 79 +- apps/opencs/model/tools/topicinfocheck.hpp | 2 +- apps/opencs/model/world/infoselectwrapper.cpp | 784 ++++++------------ apps/opencs/model/world/infoselectwrapper.hpp | 171 +--- .../model/world/nestedcoladapterimp.cpp | 57 +- .../view/world/idcompletiondelegate.cpp | 22 +- apps/openmw/mwdialogue/filter.cpp | 159 ++-- apps/openmw/mwdialogue/selectwrapper.cpp | 505 ++++------- apps/openmw/mwdialogue/selectwrapper.hpp | 62 +- components/CMakeLists.txt | 2 +- components/esm3/dialoguecondition.cpp | 204 +++++ components/esm3/dialoguecondition.hpp | 134 +++ components/esm3/loadinfo.cpp | 83 +- components/esm3/loadinfo.hpp | 15 +- 16 files changed, 1003 insertions(+), 1415 deletions(-) create mode 100644 components/esm3/dialoguecondition.cpp create mode 100644 components/esm3/dialoguecondition.hpp diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index 6def8b4a42..83b4a486e1 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -1,5 +1,6 @@ #include "labels.hpp" +#include #include #include #include @@ -572,13 +573,14 @@ std::string_view enchantTypeLabel(int idx) std::string_view ruleFunction(int idx) { - if (idx >= 0 && idx <= 72) + if (idx >= ESM::DialogueCondition::Function_FacReactionLowest + && idx <= ESM::DialogueCondition::Function_PcWerewolfKills) { static constexpr std::string_view ruleFunctions[] = { - "Reaction Low", - "Reaction High", + "Lowest Faction Reaction", + "Highest Faction Reaction", "Rank Requirement", - "NPC? Reputation", + "NPC Reputation", "Health Percent", "Player Reputation", "NPC Level", @@ -648,6 +650,7 @@ std::string_view ruleFunction(int idx) "Flee", "Should Attack", "Werewolf", + "Werewolf Kills", }; return ruleFunctions[idx]; } diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 4526b891e2..cd38dadf3f 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -57,112 +57,82 @@ namespace std::cout << " Cell Name: " << p.mCellName << std::endl; } - std::string ruleString(const ESM::DialInfo::SelectStruct& ss) + std::string ruleString(const ESM::DialogueCondition& ss) { - std::string rule = ss.mSelectRule; + std::string_view type_str = "INVALID"; + std::string_view func_str; - if (rule.length() < 5) - return "INVALID"; - - char type = rule[1]; - char indicator = rule[2]; - - std::string type_str = "INVALID"; - std::string func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1, 3)); - int func = Misc::StringUtils::toNumeric(rule.substr(2, 2), 0); - - switch (type) + switch (ss.mFunction) { - case '1': - type_str = "Function"; - func_str = std::string(ruleFunction(func)); + case ESM::DialogueCondition::Function_Global: + type_str = "Global"; + func_str = ss.mVariable; break; - case '2': - if (indicator == 's') - type_str = "Global short"; - else if (indicator == 'l') - type_str = "Global long"; - else if (indicator == 'f') - type_str = "Global float"; + case ESM::DialogueCondition::Function_Local: + type_str = "Local"; + func_str = ss.mVariable; break; - case '3': - if (indicator == 's') - type_str = "Local short"; - else if (indicator == 'l') - type_str = "Local long"; - else if (indicator == 'f') - type_str = "Local float"; + case ESM::DialogueCondition::Function_Journal: + type_str = "Journal"; + func_str = ss.mVariable; break; - case '4': - if (indicator == 'J') - type_str = "Journal"; + case ESM::DialogueCondition::Function_Item: + type_str = "Item count"; + func_str = ss.mVariable; break; - case '5': - if (indicator == 'I') - type_str = "Item type"; + case ESM::DialogueCondition::Function_Dead: + type_str = "Dead"; + func_str = ss.mVariable; break; - case '6': - if (indicator == 'D') - type_str = "NPC Dead"; + case ESM::DialogueCondition::Function_NotId: + type_str = "Not ID"; + func_str = ss.mVariable; break; - case '7': - if (indicator == 'X') - type_str = "Not ID"; + case ESM::DialogueCondition::Function_NotFaction: + type_str = "Not Faction"; + func_str = ss.mVariable; break; - case '8': - if (indicator == 'F') - type_str = "Not Faction"; + case ESM::DialogueCondition::Function_NotClass: + type_str = "Not Class"; + func_str = ss.mVariable; break; - case '9': - if (indicator == 'C') - type_str = "Not Class"; + case ESM::DialogueCondition::Function_NotRace: + type_str = "Not Race"; + func_str = ss.mVariable; break; - case 'A': - if (indicator == 'R') - type_str = "Not Race"; + case ESM::DialogueCondition::Function_NotCell: + type_str = "Not Cell"; + func_str = ss.mVariable; break; - case 'B': - if (indicator == 'L') - type_str = "Not Cell"; - break; - case 'C': - if (indicator == 's') - type_str = "Not Local"; + case ESM::DialogueCondition::Function_NotLocal: + type_str = "Not Local"; + func_str = ss.mVariable; break; default: + type_str = "Function"; + func_str = ruleFunction(ss.mFunction); break; } - // Append the variable name to the function string if any. - if (type != '1') - func_str = rule.substr(5); - - // In the previous switch, we assumed that the second char was X - // for all types not qual to one. If this wasn't true, go back to - // the error message. - if (type != '1' && rule[3] != 'X') - func_str = Misc::StringUtils::format("INVALID=%s", rule.substr(1, 3)); - - char oper = rule[4]; - std::string oper_str = "??"; - switch (oper) + std::string_view oper_str = "??"; + switch (ss.mComparison) { - case '0': + case ESM::DialogueCondition::Comp_Eq: oper_str = "=="; break; - case '1': + case ESM::DialogueCondition::Comp_Ne: oper_str = "!="; break; - case '2': + case ESM::DialogueCondition::Comp_Gt: oper_str = "> "; break; - case '3': + case ESM::DialogueCondition::Comp_Ge: oper_str = ">="; break; - case '4': + case ESM::DialogueCondition::Comp_Ls: oper_str = "< "; break; - case '5': + case ESM::DialogueCondition::Comp_Le: oper_str = "<="; break; default: @@ -170,7 +140,7 @@ namespace } std::ostringstream stream; - stream << ss.mValue; + std::visit([&](auto value) { stream << value; }, ss.mValue); std::string result = Misc::StringUtils::format("%-12s %-32s %2s %s", type_str, func_str, oper_str, stream.str()); @@ -842,7 +812,7 @@ namespace EsmTool << std::endl; std::cout << " Type: " << dialogTypeLabel(mData.mData.mType) << std::endl; - for (const ESM::DialInfo::SelectStruct& rule : mData.mSelects) + for (const auto& rule : mData.mSelects) std::cout << " Select Rule: " << ruleString(rule) << std::endl; if (!mData.mResultScript.empty()) diff --git a/apps/opencs/model/tools/topicinfocheck.cpp b/apps/opencs/model/tools/topicinfocheck.cpp index ba99a33a28..fab90d951a 100644 --- a/apps/opencs/model/tools/topicinfocheck.cpp +++ b/apps/opencs/model/tools/topicinfocheck.cpp @@ -171,10 +171,9 @@ void CSMTools::TopicInfoCheckStage::perform(int stage, CSMDoc::Messages& message // Check info conditions - for (std::vector::const_iterator it = topicInfo.mSelects.begin(); - it != topicInfo.mSelects.end(); ++it) + for (const auto& select : topicInfo.mSelects) { - verifySelectStruct((*it), id, messages); + verifySelectStruct(select, id, messages); } } @@ -308,49 +307,15 @@ bool CSMTools::TopicInfoCheckStage::verifyItem( } bool CSMTools::TopicInfoCheckStage::verifySelectStruct( - const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) + const ESM::DialogueCondition& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages) { CSMWorld::ConstInfoSelectWrapper infoCondition(select); - if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_None) + if (select.mFunction == ESM::DialogueCondition::Function_None) { messages.add(id, "Invalid condition '" + infoCondition.toString() + "'", "", CSMDoc::Message::Severity_Error); return false; } - else if (!infoCondition.variantTypeIsValid()) - { - std::ostringstream stream; - stream << "Value of condition '" << infoCondition.toString() << "' has invalid "; - - switch (select.mValue.getType()) - { - case ESM::VT_None: - stream << "None"; - break; - case ESM::VT_Short: - stream << "Short"; - break; - case ESM::VT_Int: - stream << "Int"; - break; - case ESM::VT_Long: - stream << "Long"; - break; - case ESM::VT_Float: - stream << "Float"; - break; - case ESM::VT_String: - stream << "String"; - break; - default: - stream << "unknown"; - break; - } - stream << " type"; - - messages.add(id, stream.str(), "", CSMDoc::Message::Severity_Error); - return false; - } else if (infoCondition.conditionIsAlwaysTrue()) { messages.add( @@ -365,48 +330,48 @@ bool CSMTools::TopicInfoCheckStage::verifySelectStruct( } // Id checks - if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Global - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mGlobals, id, messages)) + if (select.mFunction == ESM::DialogueCondition::Function_Global + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mGlobals, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Journal - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mJournals, id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_Journal + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mJournals, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Item - && !verifyItem(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_Item + && !verifyItem(ESM::RefId::stringRefId(select.mVariable), id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_Dead - && !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_Dead + && !verifyActor(ESM::RefId::stringRefId(select.mVariable), id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotId - && !verifyActor(ESM::RefId::stringRefId(infoCondition.getVariableName()), id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotId + && !verifyActor(ESM::RefId::stringRefId(select.mVariable), id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotFaction - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mFactions, id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotFaction + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mFactions, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotClass - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mClasses, id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotClass + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mClasses, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotRace - && !verifyId(ESM::RefId::stringRefId(infoCondition.getVariableName()), mRaces, id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotRace + && !verifyId(ESM::RefId::stringRefId(select.mVariable), mRaces, id, messages)) { return false; } - else if (infoCondition.getFunctionName() == CSMWorld::ConstInfoSelectWrapper::Function_NotCell - && !verifyCell(infoCondition.getVariableName(), id, messages)) + else if (select.mFunction == ESM::DialogueCondition::Function_NotCell + && !verifyCell(select.mVariable, id, messages)) { return false; } diff --git a/apps/opencs/model/tools/topicinfocheck.hpp b/apps/opencs/model/tools/topicinfocheck.hpp index 1bb2fc61dc..3069fbc0ff 100644 --- a/apps/opencs/model/tools/topicinfocheck.hpp +++ b/apps/opencs/model/tools/topicinfocheck.hpp @@ -84,7 +84,7 @@ namespace CSMTools const ESM::RefId& name, int rank, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifyItem(const ESM::RefId& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifySelectStruct( - const ESM::DialInfo::SelectStruct& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); + const ESM::DialogueCondition& select, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); bool verifySound(const std::string& name, const CSMWorld::UniversalId& id, CSMDoc::Messages& messages); template diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp index fb1adf16d4..d1af341c04 100644 --- a/apps/opencs/model/world/infoselectwrapper.cpp +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -6,16 +6,9 @@ #include -const size_t CSMWorld::ConstInfoSelectWrapper::RuleMinSize = 5; - -const size_t CSMWorld::ConstInfoSelectWrapper::FunctionPrefixOffset = 1; -const size_t CSMWorld::ConstInfoSelectWrapper::FunctionIndexOffset = 2; -const size_t CSMWorld::ConstInfoSelectWrapper::RelationIndexOffset = 4; -const size_t CSMWorld::ConstInfoSelectWrapper::VarNameOffset = 5; - const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = { - "Rank Low", - "Rank High", + "Faction Reaction Low", + "Faction Reaction High", "Rank Requirement", "Reputation", "Health Percent", @@ -121,17 +114,17 @@ const char* CSMWorld::ConstInfoSelectWrapper::ComparisonEnumStrings[] = { // static functions -std::string CSMWorld::ConstInfoSelectWrapper::convertToString(FunctionName name) +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ESM::DialogueCondition::Function name) { - if (name < Function_None) + if (name < ESM::DialogueCondition::Function_None) return FunctionEnumStrings[name]; else return "(Invalid Data: Function)"; } -std::string CSMWorld::ConstInfoSelectWrapper::convertToString(RelationType type) +std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ESM::DialogueCondition::Comparison type) { - if (type < Relation_None) + if (type < ESM::DialogueCondition::Comp_None) return RelationEnumStrings[type]; else return "(Invalid Data: Relation)"; @@ -147,20 +140,21 @@ std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ComparisonType typ // ConstInfoSelectWrapper -CSMWorld::ConstInfoSelectWrapper::ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select) +CSMWorld::ConstInfoSelectWrapper::ConstInfoSelectWrapper(const ESM::DialogueCondition& select) : mConstSelect(select) { - readRule(); + updateHasVariable(); + updateComparisonType(); } -CSMWorld::ConstInfoSelectWrapper::FunctionName CSMWorld::ConstInfoSelectWrapper::getFunctionName() const +ESM::DialogueCondition::Function CSMWorld::ConstInfoSelectWrapper::getFunctionName() const { - return mFunctionName; + return mConstSelect.mFunction; } -CSMWorld::ConstInfoSelectWrapper::RelationType CSMWorld::ConstInfoSelectWrapper::getRelationType() const +ESM::DialogueCondition::Comparison CSMWorld::ConstInfoSelectWrapper::getRelationType() const { - return mRelationType; + return mConstSelect.mComparison; } CSMWorld::ConstInfoSelectWrapper::ComparisonType CSMWorld::ConstInfoSelectWrapper::getComparisonType() const @@ -175,24 +169,21 @@ bool CSMWorld::ConstInfoSelectWrapper::hasVariable() const const std::string& CSMWorld::ConstInfoSelectWrapper::getVariableName() const { - return mVariableName; + return mConstSelect.mVariable; } bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue() const { - if (!variantTypeIsValid()) - return false; - if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) { - if (mConstSelect.mValue.getType() == ESM::VT_Float) + if (std::holds_alternative(mConstSelect.mValue)) return conditionIsAlwaysTrue(getConditionFloatRange(), getValidIntRange()); else return conditionIsAlwaysTrue(getConditionIntRange(), getValidIntRange()); } else if (mComparisonType == Comparison_Numeric) { - if (mConstSelect.mValue.getType() == ESM::VT_Float) + if (std::holds_alternative(mConstSelect.mValue)) return conditionIsAlwaysTrue(getConditionFloatRange(), getValidFloatRange()); else return conditionIsAlwaysTrue(getConditionIntRange(), getValidFloatRange()); @@ -203,19 +194,16 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue() const bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const { - if (!variantTypeIsValid()) - return false; - if (mComparisonType == Comparison_Boolean || mComparisonType == Comparison_Integer) { - if (mConstSelect.mValue.getType() == ESM::VT_Float) + if (std::holds_alternative(mConstSelect.mValue)) return conditionIsNeverTrue(getConditionFloatRange(), getValidIntRange()); else return conditionIsNeverTrue(getConditionIntRange(), getValidIntRange()); } else if (mComparisonType == Comparison_Numeric) { - if (mConstSelect.mValue.getType() == ESM::VT_Float) + if (std::holds_alternative(mConstSelect.mValue)) return conditionIsNeverTrue(getConditionFloatRange(), getValidFloatRange()); else return conditionIsNeverTrue(getConditionIntRange(), getValidFloatRange()); @@ -224,170 +212,36 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue() const return false; } -bool CSMWorld::ConstInfoSelectWrapper::variantTypeIsValid() const -{ - return (mConstSelect.mValue.getType() == ESM::VT_Int || mConstSelect.mValue.getType() == ESM::VT_Float); -} - -const ESM::Variant& CSMWorld::ConstInfoSelectWrapper::getVariant() const -{ - return mConstSelect.mValue; -} - std::string CSMWorld::ConstInfoSelectWrapper::toString() const { std::ostringstream stream; - stream << convertToString(mFunctionName) << " "; + stream << convertToString(getFunctionName()) << " "; if (mHasVariable) - stream << mVariableName << " "; + stream << getVariableName() << " "; - stream << convertToString(mRelationType) << " "; + stream << convertToString(getRelationType()) << " "; - switch (mConstSelect.mValue.getType()) - { - case ESM::VT_Int: - stream << mConstSelect.mValue.getInteger(); - break; - - case ESM::VT_Float: - stream << mConstSelect.mValue.getFloat(); - break; - - default: - stream << "(Invalid value type)"; - break; - } + std::visit([&](auto value) { stream << value; }, mConstSelect.mValue); return stream.str(); } -void CSMWorld::ConstInfoSelectWrapper::readRule() -{ - if (mConstSelect.mSelectRule.size() < RuleMinSize) - throw std::runtime_error("InfoSelectWrapper: rule is to small"); - - readFunctionName(); - readRelationType(); - readVariableName(); - updateHasVariable(); - updateComparisonType(); -} - -void CSMWorld::ConstInfoSelectWrapper::readFunctionName() -{ - char functionPrefix = mConstSelect.mSelectRule[FunctionPrefixOffset]; - std::string functionIndex = mConstSelect.mSelectRule.substr(FunctionIndexOffset, 2); - int convertedIndex = -1; - - // Read in function index, form ## from 00 .. 73, skip leading zero - if (functionIndex[0] == '0') - functionIndex = functionIndex[1]; - - std::stringstream stream; - stream << functionIndex; - stream >> convertedIndex; - - switch (functionPrefix) - { - case '1': - if (convertedIndex >= 0 && convertedIndex <= 73) - mFunctionName = static_cast(convertedIndex); - else - mFunctionName = Function_None; - break; - - case '2': - mFunctionName = Function_Global; - break; - case '3': - mFunctionName = Function_Local; - break; - case '4': - mFunctionName = Function_Journal; - break; - case '5': - mFunctionName = Function_Item; - break; - case '6': - mFunctionName = Function_Dead; - break; - case '7': - mFunctionName = Function_NotId; - break; - case '8': - mFunctionName = Function_NotFaction; - break; - case '9': - mFunctionName = Function_NotClass; - break; - case 'A': - mFunctionName = Function_NotRace; - break; - case 'B': - mFunctionName = Function_NotCell; - break; - case 'C': - mFunctionName = Function_NotLocal; - break; - default: - mFunctionName = Function_None; - break; - } -} - -void CSMWorld::ConstInfoSelectWrapper::readRelationType() -{ - char relationIndex = mConstSelect.mSelectRule[RelationIndexOffset]; - - switch (relationIndex) - { - case '0': - mRelationType = Relation_Equal; - break; - case '1': - mRelationType = Relation_NotEqual; - break; - case '2': - mRelationType = Relation_Greater; - break; - case '3': - mRelationType = Relation_GreaterOrEqual; - break; - case '4': - mRelationType = Relation_Less; - break; - case '5': - mRelationType = Relation_LessOrEqual; - break; - default: - mRelationType = Relation_None; - } -} - -void CSMWorld::ConstInfoSelectWrapper::readVariableName() -{ - if (mConstSelect.mSelectRule.size() >= VarNameOffset) - mVariableName = mConstSelect.mSelectRule.substr(VarNameOffset); - else - mVariableName.clear(); -} - void CSMWorld::ConstInfoSelectWrapper::updateHasVariable() { - switch (mFunctionName) + switch (getFunctionName()) { - case Function_Global: - case Function_Local: - case Function_Journal: - case Function_Item: - case Function_Dead: - case Function_NotId: - case Function_NotFaction: - case Function_NotClass: - case Function_NotRace: - case Function_NotCell: - case Function_NotLocal: + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_Journal: + case ESM::DialogueCondition::Function_Item: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_NotId: + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_NotCell: + case ESM::DialogueCondition::Function_NotLocal: mHasVariable = true; break; @@ -399,103 +253,103 @@ void CSMWorld::ConstInfoSelectWrapper::updateHasVariable() void CSMWorld::ConstInfoSelectWrapper::updateComparisonType() { - switch (mFunctionName) + switch (getFunctionName()) { // Boolean - case Function_NotId: - case Function_NotFaction: - case Function_NotClass: - case Function_NotRace: - case Function_NotCell: - case Function_PcExpelled: - case Function_PcCommonDisease: - case Function_PcBlightDisease: - case Function_SameSex: - case Function_SameRace: - case Function_SameFaction: - case Function_Detected: - case Function_Alarmed: - case Function_PcCorpus: - case Function_PcVampire: - case Function_Attacked: - case Function_TalkedToPc: - case Function_ShouldAttack: - case Function_Werewolf: + case ESM::DialogueCondition::Function_NotId: + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_NotCell: + case ESM::DialogueCondition::Function_PcExpelled: + case ESM::DialogueCondition::Function_PcCommonDisease: + case ESM::DialogueCondition::Function_PcBlightDisease: + case ESM::DialogueCondition::Function_SameSex: + case ESM::DialogueCondition::Function_SameRace: + case ESM::DialogueCondition::Function_SameFaction: + case ESM::DialogueCondition::Function_Detected: + case ESM::DialogueCondition::Function_Alarmed: + case ESM::DialogueCondition::Function_PcCorpus: + case ESM::DialogueCondition::Function_PcVampire: + case ESM::DialogueCondition::Function_Attacked: + case ESM::DialogueCondition::Function_TalkedToPc: + case ESM::DialogueCondition::Function_ShouldAttack: + case ESM::DialogueCondition::Function_Werewolf: mComparisonType = Comparison_Boolean; break; // Integer - case Function_Journal: - case Function_Item: - case Function_Dead: - case Function_RankLow: - case Function_RankHigh: - case Function_RankRequirement: - case Function_Reputation: - case Function_PcReputation: - case Function_PcLevel: - case Function_PcStrength: - case Function_PcBlock: - case Function_PcArmorer: - case Function_PcMediumArmor: - case Function_PcHeavyArmor: - case Function_PcBluntWeapon: - case Function_PcLongBlade: - case Function_PcAxe: - case Function_PcSpear: - case Function_PcAthletics: - case Function_PcEnchant: - case Function_PcDestruction: - case Function_PcAlteration: - case Function_PcIllusion: - case Function_PcConjuration: - case Function_PcMysticism: - case Function_PcRestoration: - case Function_PcAlchemy: - case Function_PcUnarmored: - case Function_PcSecurity: - case Function_PcSneak: - case Function_PcAcrobatics: - case Function_PcLightArmor: - case Function_PcShortBlade: - case Function_PcMarksman: - case Function_PcMerchantile: - case Function_PcSpeechcraft: - case Function_PcHandToHand: - case Function_PcGender: - case Function_PcClothingModifier: - case Function_PcCrimeLevel: - case Function_FactionRankDifference: - case Function_Choice: - case Function_PcIntelligence: - case Function_PcWillpower: - case Function_PcAgility: - case Function_PcSpeed: - case Function_PcEndurance: - case Function_PcPersonality: - case Function_PcLuck: - case Function_Weather: - case Function_Level: - case Function_CreatureTarget: - case Function_FriendHit: - case Function_Fight: - case Function_Hello: - case Function_Alarm: - case Function_Flee: - case Function_PcWerewolfKills: + case ESM::DialogueCondition::Function_Journal: + case ESM::DialogueCondition::Function_Item: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: + case ESM::DialogueCondition::Function_RankRequirement: + case ESM::DialogueCondition::Function_Reputation: + case ESM::DialogueCondition::Function_PcReputation: + case ESM::DialogueCondition::Function_PcLevel: + case ESM::DialogueCondition::Function_PcStrength: + case ESM::DialogueCondition::Function_PcBlock: + case ESM::DialogueCondition::Function_PcArmorer: + case ESM::DialogueCondition::Function_PcMediumArmor: + case ESM::DialogueCondition::Function_PcHeavyArmor: + case ESM::DialogueCondition::Function_PcBluntWeapon: + case ESM::DialogueCondition::Function_PcLongBlade: + case ESM::DialogueCondition::Function_PcAxe: + case ESM::DialogueCondition::Function_PcSpear: + case ESM::DialogueCondition::Function_PcAthletics: + case ESM::DialogueCondition::Function_PcEnchant: + case ESM::DialogueCondition::Function_PcDestruction: + case ESM::DialogueCondition::Function_PcAlteration: + case ESM::DialogueCondition::Function_PcIllusion: + case ESM::DialogueCondition::Function_PcConjuration: + case ESM::DialogueCondition::Function_PcMysticism: + case ESM::DialogueCondition::Function_PcRestoration: + case ESM::DialogueCondition::Function_PcAlchemy: + case ESM::DialogueCondition::Function_PcUnarmored: + case ESM::DialogueCondition::Function_PcSecurity: + case ESM::DialogueCondition::Function_PcSneak: + case ESM::DialogueCondition::Function_PcAcrobatics: + case ESM::DialogueCondition::Function_PcLightArmor: + case ESM::DialogueCondition::Function_PcShortBlade: + case ESM::DialogueCondition::Function_PcMarksman: + case ESM::DialogueCondition::Function_PcMerchantile: + case ESM::DialogueCondition::Function_PcSpeechcraft: + case ESM::DialogueCondition::Function_PcHandToHand: + case ESM::DialogueCondition::Function_PcGender: + case ESM::DialogueCondition::Function_PcClothingModifier: + case ESM::DialogueCondition::Function_PcCrimeLevel: + case ESM::DialogueCondition::Function_FactionRankDifference: + case ESM::DialogueCondition::Function_Choice: + case ESM::DialogueCondition::Function_PcIntelligence: + case ESM::DialogueCondition::Function_PcWillpower: + case ESM::DialogueCondition::Function_PcAgility: + case ESM::DialogueCondition::Function_PcSpeed: + case ESM::DialogueCondition::Function_PcEndurance: + case ESM::DialogueCondition::Function_PcPersonality: + case ESM::DialogueCondition::Function_PcLuck: + case ESM::DialogueCondition::Function_Weather: + case ESM::DialogueCondition::Function_Level: + case ESM::DialogueCondition::Function_CreatureTarget: + case ESM::DialogueCondition::Function_FriendHit: + case ESM::DialogueCondition::Function_Fight: + case ESM::DialogueCondition::Function_Hello: + case ESM::DialogueCondition::Function_Alarm: + case ESM::DialogueCondition::Function_Flee: + case ESM::DialogueCondition::Function_PcWerewolfKills: mComparisonType = Comparison_Integer; break; // Numeric - case Function_Global: - case Function_Local: - case Function_NotLocal: - - case Function_Health_Percent: - case Function_PcHealthPercent: - case Function_PcMagicka: - case Function_PcFatigue: - case Function_PcHealth: + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: + + case ESM::DialogueCondition::Function_Health_Percent: + case ESM::DialogueCondition::Function_PcHealthPercent: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealth: mComparisonType = Comparison_Numeric; break; @@ -511,15 +365,15 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() con const int IntMin = std::numeric_limits::min(); const std::pair InvalidRange(IntMax, IntMin); - int value = mConstSelect.mValue.getInteger(); + int value = std::get(mConstSelect.mValue); - switch (mRelationType) + switch (getRelationType()) { - case Relation_Equal: - case Relation_NotEqual: + case ESM::DialogueCondition::Comp_Eq: + case ESM::DialogueCondition::Comp_Ne: return std::pair(value, value); - case Relation_Greater: + case ESM::DialogueCondition::Comp_Gt: if (value == IntMax) { return InvalidRange; @@ -530,10 +384,10 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() con } break; - case Relation_GreaterOrEqual: + case ESM::DialogueCondition::Comp_Ge: return std::pair(value, IntMax); - case Relation_Less: + case ESM::DialogueCondition::Comp_Ls: if (value == IntMin) { return InvalidRange; @@ -543,7 +397,7 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getConditionIntRange() con return std::pair(IntMin, value - 1); } - case Relation_LessOrEqual: + case ESM::DialogueCondition::Comp_Le: return std::pair(IntMin, value); default: @@ -557,24 +411,24 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getConditionFloatRange const float FloatMin = -std::numeric_limits::infinity(); const float Epsilon = std::numeric_limits::epsilon(); - float value = mConstSelect.mValue.getFloat(); + float value = std::get(mConstSelect.mValue); - switch (mRelationType) + switch (getRelationType()) { - case Relation_Equal: - case Relation_NotEqual: + case ESM::DialogueCondition::Comp_Eq: + case ESM::DialogueCondition::Comp_Ne: return std::pair(value, value); - case Relation_Greater: + case ESM::DialogueCondition::Comp_Gt: return std::pair(value + Epsilon, FloatMax); - case Relation_GreaterOrEqual: + case ESM::DialogueCondition::Comp_Ge: return std::pair(value, FloatMax); - case Relation_Less: + case ESM::DialogueCondition::Comp_Ls: return std::pair(FloatMin, value - Epsilon); - case Relation_LessOrEqual: + case ESM::DialogueCondition::Comp_Le: return std::pair(FloatMin, value); default: @@ -587,120 +441,120 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const const int IntMax = std::numeric_limits::max(); const int IntMin = std::numeric_limits::min(); - switch (mFunctionName) + switch (getFunctionName()) { // Boolean - case Function_NotId: - case Function_NotFaction: - case Function_NotClass: - case Function_NotRace: - case Function_NotCell: - case Function_PcExpelled: - case Function_PcCommonDisease: - case Function_PcBlightDisease: - case Function_SameSex: - case Function_SameRace: - case Function_SameFaction: - case Function_Detected: - case Function_Alarmed: - case Function_PcCorpus: - case Function_PcVampire: - case Function_Attacked: - case Function_TalkedToPc: - case Function_ShouldAttack: - case Function_Werewolf: + case ESM::DialogueCondition::Function_NotId: + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_NotCell: + case ESM::DialogueCondition::Function_PcExpelled: + case ESM::DialogueCondition::Function_PcCommonDisease: + case ESM::DialogueCondition::Function_PcBlightDisease: + case ESM::DialogueCondition::Function_SameSex: + case ESM::DialogueCondition::Function_SameRace: + case ESM::DialogueCondition::Function_SameFaction: + case ESM::DialogueCondition::Function_Detected: + case ESM::DialogueCondition::Function_Alarmed: + case ESM::DialogueCondition::Function_PcCorpus: + case ESM::DialogueCondition::Function_PcVampire: + case ESM::DialogueCondition::Function_Attacked: + case ESM::DialogueCondition::Function_TalkedToPc: + case ESM::DialogueCondition::Function_ShouldAttack: + case ESM::DialogueCondition::Function_Werewolf: return std::pair(0, 1); // Integer - case Function_RankLow: - case Function_RankHigh: - case Function_Reputation: - case Function_PcReputation: - case Function_Journal: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: + case ESM::DialogueCondition::Function_Reputation: + case ESM::DialogueCondition::Function_PcReputation: + case ESM::DialogueCondition::Function_Journal: return std::pair(IntMin, IntMax); - case Function_Item: - case Function_Dead: - case Function_PcLevel: - case Function_PcStrength: - case Function_PcBlock: - case Function_PcArmorer: - case Function_PcMediumArmor: - case Function_PcHeavyArmor: - case Function_PcBluntWeapon: - case Function_PcLongBlade: - case Function_PcAxe: - case Function_PcSpear: - case Function_PcAthletics: - case Function_PcEnchant: - case Function_PcDestruction: - case Function_PcAlteration: - case Function_PcIllusion: - case Function_PcConjuration: - case Function_PcMysticism: - case Function_PcRestoration: - case Function_PcAlchemy: - case Function_PcUnarmored: - case Function_PcSecurity: - case Function_PcSneak: - case Function_PcAcrobatics: - case Function_PcLightArmor: - case Function_PcShortBlade: - case Function_PcMarksman: - case Function_PcMerchantile: - case Function_PcSpeechcraft: - case Function_PcHandToHand: - case Function_PcClothingModifier: - case Function_PcCrimeLevel: - case Function_Choice: - case Function_PcIntelligence: - case Function_PcWillpower: - case Function_PcAgility: - case Function_PcSpeed: - case Function_PcEndurance: - case Function_PcPersonality: - case Function_PcLuck: - case Function_Level: - case Function_PcWerewolfKills: + case ESM::DialogueCondition::Function_Item: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_PcLevel: + case ESM::DialogueCondition::Function_PcStrength: + case ESM::DialogueCondition::Function_PcBlock: + case ESM::DialogueCondition::Function_PcArmorer: + case ESM::DialogueCondition::Function_PcMediumArmor: + case ESM::DialogueCondition::Function_PcHeavyArmor: + case ESM::DialogueCondition::Function_PcBluntWeapon: + case ESM::DialogueCondition::Function_PcLongBlade: + case ESM::DialogueCondition::Function_PcAxe: + case ESM::DialogueCondition::Function_PcSpear: + case ESM::DialogueCondition::Function_PcAthletics: + case ESM::DialogueCondition::Function_PcEnchant: + case ESM::DialogueCondition::Function_PcDestruction: + case ESM::DialogueCondition::Function_PcAlteration: + case ESM::DialogueCondition::Function_PcIllusion: + case ESM::DialogueCondition::Function_PcConjuration: + case ESM::DialogueCondition::Function_PcMysticism: + case ESM::DialogueCondition::Function_PcRestoration: + case ESM::DialogueCondition::Function_PcAlchemy: + case ESM::DialogueCondition::Function_PcUnarmored: + case ESM::DialogueCondition::Function_PcSecurity: + case ESM::DialogueCondition::Function_PcSneak: + case ESM::DialogueCondition::Function_PcAcrobatics: + case ESM::DialogueCondition::Function_PcLightArmor: + case ESM::DialogueCondition::Function_PcShortBlade: + case ESM::DialogueCondition::Function_PcMarksman: + case ESM::DialogueCondition::Function_PcMerchantile: + case ESM::DialogueCondition::Function_PcSpeechcraft: + case ESM::DialogueCondition::Function_PcHandToHand: + case ESM::DialogueCondition::Function_PcClothingModifier: + case ESM::DialogueCondition::Function_PcCrimeLevel: + case ESM::DialogueCondition::Function_Choice: + case ESM::DialogueCondition::Function_PcIntelligence: + case ESM::DialogueCondition::Function_PcWillpower: + case ESM::DialogueCondition::Function_PcAgility: + case ESM::DialogueCondition::Function_PcSpeed: + case ESM::DialogueCondition::Function_PcEndurance: + case ESM::DialogueCondition::Function_PcPersonality: + case ESM::DialogueCondition::Function_PcLuck: + case ESM::DialogueCondition::Function_Level: + case ESM::DialogueCondition::Function_PcWerewolfKills: return std::pair(0, IntMax); - case Function_Fight: - case Function_Hello: - case Function_Alarm: - case Function_Flee: + case ESM::DialogueCondition::Function_Fight: + case ESM::DialogueCondition::Function_Hello: + case ESM::DialogueCondition::Function_Alarm: + case ESM::DialogueCondition::Function_Flee: return std::pair(0, 100); - case Function_Weather: + case ESM::DialogueCondition::Function_Weather: return std::pair(0, 9); - case Function_FriendHit: + case ESM::DialogueCondition::Function_FriendHit: return std::pair(0, 4); - case Function_RankRequirement: + case ESM::DialogueCondition::Function_RankRequirement: return std::pair(0, 3); - case Function_CreatureTarget: + case ESM::DialogueCondition::Function_CreatureTarget: return std::pair(0, 2); - case Function_PcGender: + case ESM::DialogueCondition::Function_PcGender: return std::pair(0, 1); - case Function_FactionRankDifference: + case ESM::DialogueCondition::Function_FactionRankDifference: return std::pair(-9, 9); // Numeric - case Function_Global: - case Function_Local: - case Function_NotLocal: + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: return std::pair(IntMin, IntMax); - case Function_PcMagicka: - case Function_PcFatigue: - case Function_PcHealth: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealth: return std::pair(0, IntMax); - case Function_Health_Percent: - case Function_PcHealthPercent: + case ESM::DialogueCondition::Function_Health_Percent: + case ESM::DialogueCondition::Function_PcHealthPercent: return std::pair(0, 100); default: @@ -713,21 +567,21 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidFloatRange() c const float FloatMax = std::numeric_limits::infinity(); const float FloatMin = -std::numeric_limits::infinity(); - switch (mFunctionName) + switch (getFunctionName()) { // Numeric - case Function_Global: - case Function_Local: - case Function_NotLocal: + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: return std::pair(FloatMin, FloatMax); - case Function_PcMagicka: - case Function_PcFatigue: - case Function_PcHealth: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealth: return std::pair(0, FloatMax); - case Function_Health_Percent: - case Function_PcHealthPercent: + case ESM::DialogueCondition::Function_Health_Percent: + case ESM::DialogueCondition::Function_PcHealthPercent: return std::pair(0, 100); default: @@ -768,19 +622,19 @@ template bool CSMWorld::ConstInfoSelectWrapper::conditionIsAlwaysTrue( std::pair conditionRange, std::pair validRange) const { - switch (mRelationType) + switch (getRelationType()) { - case Relation_Equal: + case ESM::DialogueCondition::Comp_Eq: return false; - case Relation_NotEqual: + case ESM::DialogueCondition::Comp_Ne: // If value is not within range, it will always be true return !rangeContains(conditionRange.first, validRange); - case Relation_Greater: - case Relation_GreaterOrEqual: - case Relation_Less: - case Relation_LessOrEqual: + case ESM::DialogueCondition::Comp_Gt: + case ESM::DialogueCondition::Comp_Ge: + case ESM::DialogueCondition::Comp_Ls: + case ESM::DialogueCondition::Comp_Le: // If the valid range is completely within the condition range, it will always be true return rangeFullyContains(conditionRange, validRange); @@ -795,18 +649,18 @@ template bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue( std::pair conditionRange, std::pair validRange) const { - switch (mRelationType) + switch (getRelationType()) { - case Relation_Equal: + case ESM::DialogueCondition::Comp_Eq: return !rangeContains(conditionRange.first, validRange); - case Relation_NotEqual: + case ESM::DialogueCondition::Comp_Ne: return false; - case Relation_Greater: - case Relation_GreaterOrEqual: - case Relation_Less: - case Relation_LessOrEqual: + case ESM::DialogueCondition::Comp_Gt: + case ESM::DialogueCondition::Comp_Ge: + case ESM::DialogueCondition::Comp_Ls: + case ESM::DialogueCondition::Comp_Le: // If ranges do not overlap, it will never be true return !rangesOverlap(conditionRange, validRange); @@ -817,153 +671,47 @@ bool CSMWorld::ConstInfoSelectWrapper::conditionIsNeverTrue( return false; } +QVariant CSMWorld::ConstInfoSelectWrapper::getValue() const +{ + return std::visit([](auto value) { return QVariant(value); }, mConstSelect.mValue); +} + // InfoSelectWrapper -CSMWorld::InfoSelectWrapper::InfoSelectWrapper(ESM::DialInfo::SelectStruct& select) +CSMWorld::InfoSelectWrapper::InfoSelectWrapper(ESM::DialogueCondition& select) : CSMWorld::ConstInfoSelectWrapper(select) , mSelect(select) { } -void CSMWorld::InfoSelectWrapper::setFunctionName(FunctionName name) +void CSMWorld::InfoSelectWrapper::setFunctionName(ESM::DialogueCondition::Function name) { - mFunctionName = name; + mSelect.mFunction = name; updateHasVariable(); updateComparisonType(); + if (getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric + && std::holds_alternative(mSelect.mValue)) + { + mSelect.mValue = std::visit([](auto value) { return static_cast(value); }, mSelect.mValue); + } } -void CSMWorld::InfoSelectWrapper::setRelationType(RelationType type) +void CSMWorld::InfoSelectWrapper::setRelationType(ESM::DialogueCondition::Comparison type) { - mRelationType = type; + mSelect.mComparison = type; } void CSMWorld::InfoSelectWrapper::setVariableName(const std::string& name) { - mVariableName = name; + mSelect.mVariable = name; } -void CSMWorld::InfoSelectWrapper::setDefaults() +void CSMWorld::InfoSelectWrapper::setValue(int value) { - if (!variantTypeIsValid()) - mSelect.mValue.setType(ESM::VT_Int); - - switch (mComparisonType) - { - case Comparison_Boolean: - setRelationType(Relation_Equal); - mSelect.mValue.setInteger(1); - break; - - case Comparison_Integer: - case Comparison_Numeric: - setRelationType(Relation_Greater); - mSelect.mValue.setInteger(0); - break; - - default: - // Do nothing - break; - } - - update(); -} - -void CSMWorld::InfoSelectWrapper::update() -{ - std::ostringstream stream; - - // Leading 0 - stream << '0'; - - // Write Function - - bool writeIndex = false; - size_t functionIndex = static_cast(mFunctionName); - - switch (mFunctionName) - { - case Function_None: - stream << '0'; - break; - case Function_Global: - stream << '2'; - break; - case Function_Local: - stream << '3'; - break; - case Function_Journal: - stream << '4'; - break; - case Function_Item: - stream << '5'; - break; - case Function_Dead: - stream << '6'; - break; - case Function_NotId: - stream << '7'; - break; - case Function_NotFaction: - stream << '8'; - break; - case Function_NotClass: - stream << '9'; - break; - case Function_NotRace: - stream << 'A'; - break; - case Function_NotCell: - stream << 'B'; - break; - case Function_NotLocal: - stream << 'C'; - break; - default: - stream << '1'; - writeIndex = true; - break; - } - - if (writeIndex && functionIndex < 10) // leading 0 - stream << '0' << functionIndex; - else if (writeIndex) - stream << functionIndex; - else - stream << "00"; - - // Write Relation - switch (mRelationType) - { - case Relation_Equal: - stream << '0'; - break; - case Relation_NotEqual: - stream << '1'; - break; - case Relation_Greater: - stream << '2'; - break; - case Relation_GreaterOrEqual: - stream << '3'; - break; - case Relation_Less: - stream << '4'; - break; - case Relation_LessOrEqual: - stream << '5'; - break; - default: - stream << '0'; - break; - } - - if (mHasVariable) - stream << mVariableName; - - mSelect.mSelectRule = stream.str(); + mSelect.mValue = value; } -ESM::Variant& CSMWorld::InfoSelectWrapper::getVariant() +void CSMWorld::InfoSelectWrapper::setValue(float value) { - return mSelect.mValue; + mSelect.mValue = value; } diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp index 4ecdc676dc..d8d108444f 100644 --- a/apps/opencs/model/world/infoselectwrapper.hpp +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -7,133 +7,13 @@ #include -namespace ESM -{ - class Variant; -} +#include namespace CSMWorld { - // ESM::DialInfo::SelectStruct.mSelectRule - // 012345... - // ^^^ ^^ - // ||| || - // ||| |+------------- condition variable string - // ||| +-------------- comparison type, ['0'..'5']; e.g. !=, <, >=, etc - // ||+---------------- function index (encoded, where function == '1') - // |+----------------- function, ['1'..'C']; e.g. Global, Local, Not ID, etc - // +------------------ unknown - // - - // Wrapper for DialInfo::SelectStruct class ConstInfoSelectWrapper { public: - // Order matters - enum FunctionName - { - Function_RankLow = 0, - Function_RankHigh, - Function_RankRequirement, - Function_Reputation, - Function_Health_Percent, - Function_PcReputation, - Function_PcLevel, - Function_PcHealthPercent, - Function_PcMagicka, - Function_PcFatigue, - Function_PcStrength, - Function_PcBlock, - Function_PcArmorer, - Function_PcMediumArmor, - Function_PcHeavyArmor, - Function_PcBluntWeapon, - Function_PcLongBlade, - Function_PcAxe, - Function_PcSpear, - Function_PcAthletics, - Function_PcEnchant, - Function_PcDestruction, - Function_PcAlteration, - Function_PcIllusion, - Function_PcConjuration, - Function_PcMysticism, - Function_PcRestoration, - Function_PcAlchemy, - Function_PcUnarmored, - Function_PcSecurity, - Function_PcSneak, - Function_PcAcrobatics, - Function_PcLightArmor, - Function_PcShortBlade, - Function_PcMarksman, - Function_PcMerchantile, - Function_PcSpeechcraft, - Function_PcHandToHand, - Function_PcGender, - Function_PcExpelled, - Function_PcCommonDisease, - Function_PcBlightDisease, - Function_PcClothingModifier, - Function_PcCrimeLevel, - Function_SameSex, - Function_SameRace, - Function_SameFaction, - Function_FactionRankDifference, - Function_Detected, - Function_Alarmed, - Function_Choice, - Function_PcIntelligence, - Function_PcWillpower, - Function_PcAgility, - Function_PcSpeed, - Function_PcEndurance, - Function_PcPersonality, - Function_PcLuck, - Function_PcCorpus, - Function_Weather, - Function_PcVampire, - Function_Level, - Function_Attacked, - Function_TalkedToPc, - Function_PcHealth, - Function_CreatureTarget, - Function_FriendHit, - Function_Fight, - Function_Hello, - Function_Alarm, - Function_Flee, - Function_ShouldAttack, - Function_Werewolf, - Function_PcWerewolfKills = 73, - - Function_Global, - Function_Local, - Function_Journal, - Function_Item, - Function_Dead, - Function_NotId, - Function_NotFaction, - Function_NotClass, - Function_NotRace, - Function_NotCell, - Function_NotLocal, - - Function_None - }; - - enum RelationType - { - Relation_Equal, - Relation_NotEqual, - Relation_Greater, - Relation_GreaterOrEqual, - Relation_Less, - Relation_LessOrEqual, - - Relation_None - }; - enum ComparisonType { Comparison_Boolean, @@ -143,25 +23,18 @@ namespace CSMWorld Comparison_None }; - static const size_t RuleMinSize; - - static const size_t FunctionPrefixOffset; - static const size_t FunctionIndexOffset; - static const size_t RelationIndexOffset; - static const size_t VarNameOffset; - static const char* FunctionEnumStrings[]; static const char* RelationEnumStrings[]; static const char* ComparisonEnumStrings[]; - static std::string convertToString(FunctionName name); - static std::string convertToString(RelationType type); + static std::string convertToString(ESM::DialogueCondition::Function name); + static std::string convertToString(ESM::DialogueCondition::Comparison type); static std::string convertToString(ComparisonType type); - ConstInfoSelectWrapper(const ESM::DialInfo::SelectStruct& select); + ConstInfoSelectWrapper(const ESM::DialogueCondition& select); - FunctionName getFunctionName() const; - RelationType getRelationType() const; + ESM::DialogueCondition::Function getFunctionName() const; + ESM::DialogueCondition::Comparison getRelationType() const; ComparisonType getComparisonType() const; bool hasVariable() const; @@ -169,17 +42,12 @@ namespace CSMWorld bool conditionIsAlwaysTrue() const; bool conditionIsNeverTrue() const; - bool variantTypeIsValid() const; - const ESM::Variant& getVariant() const; + QVariant getValue() const; std::string toString() const; protected: - void readRule(); - void readFunctionName(); - void readRelationType(); - void readVariableName(); void updateHasVariable(); void updateComparisonType(); @@ -207,38 +75,29 @@ namespace CSMWorld template bool conditionIsNeverTrue(std::pair conditionRange, std::pair validRange) const; - FunctionName mFunctionName; - RelationType mRelationType; ComparisonType mComparisonType; bool mHasVariable; - std::string mVariableName; private: - const ESM::DialInfo::SelectStruct& mConstSelect; + const ESM::DialogueCondition& mConstSelect; }; - // Wrapper for DialInfo::SelectStruct that can modify the wrapped select struct + // Wrapper for DialogueCondition that can modify the wrapped select struct class InfoSelectWrapper : public ConstInfoSelectWrapper { public: - InfoSelectWrapper(ESM::DialInfo::SelectStruct& select); + InfoSelectWrapper(ESM::DialogueCondition& select); // Wrapped SelectStruct will not be modified until update() is called - void setFunctionName(FunctionName name); - void setRelationType(RelationType type); + void setFunctionName(ESM::DialogueCondition::Function name); + void setRelationType(ESM::DialogueCondition::Comparison type); void setVariableName(const std::string& name); - - // Modified wrapped SelectStruct - void update(); - - // This sets properties based on the function name to its defaults and updates the wrapped object - void setDefaults(); - - ESM::Variant& getVariant(); + void setValue(int value); + void setValue(float value); private: - ESM::DialInfo::SelectStruct& mSelect; + ESM::DialogueCondition& mSelect; void writeRule(); }; diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 0d90ea0d68..86d38f9cd2 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -538,13 +538,11 @@ namespace CSMWorld { Info info = record.get(); - std::vector& conditions = info.mSelects; + auto& conditions = info.mSelects; // default row - ESM::DialInfo::SelectStruct condStruct; - condStruct.mSelectRule = "01000"; - condStruct.mValue = ESM::Variant(); - condStruct.mValue.setType(ESM::VT_Int); + ESM::DialogueCondition condStruct; + condStruct.mIndex = conditions.size(); conditions.insert(conditions.begin() + position, condStruct); @@ -555,7 +553,7 @@ namespace CSMWorld { Info info = record.get(); - std::vector& conditions = info.mSelects; + auto& conditions = info.mSelects; if (rowToRemove < 0 || rowToRemove >= static_cast(conditions.size())) throw std::runtime_error("index out of range"); @@ -569,8 +567,8 @@ namespace CSMWorld { Info info = record.get(); - info.mSelects = static_cast>&>(nestedTable) - .mNestedTable; + info.mSelects + = static_cast>&>(nestedTable).mNestedTable; record.setModified(info); } @@ -578,14 +576,14 @@ namespace CSMWorld NestedTableWrapperBase* InfoConditionAdapter::table(const Record& record) const { // deleted by dtor of NestedTableStoring - return new NestedTableWrapper>(record.get().mSelects); + return new NestedTableWrapper>(record.get().mSelects); } QVariant InfoConditionAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { Info info = record.get(); - std::vector& conditions = info.mSelects; + auto& conditions = info.mSelects; if (subRowIndex < 0 || subRowIndex >= static_cast(conditions.size())) throw std::runtime_error("index out of range"); @@ -611,19 +609,7 @@ namespace CSMWorld } case 3: { - switch (infoSelectWrapper.getVariant().getType()) - { - case ESM::VT_Int: - { - return infoSelectWrapper.getVariant().getInteger(); - } - case ESM::VT_Float: - { - return infoSelectWrapper.getVariant().getFloat(); - } - default: - return QVariant(); - } + return infoSelectWrapper.getValue(); } default: throw std::runtime_error("Info condition subcolumn index out of range"); @@ -635,7 +621,7 @@ namespace CSMWorld { Info info = record.get(); - std::vector& conditions = info.mSelects; + auto& conditions = info.mSelects; if (subRowIndex < 0 || subRowIndex >= static_cast(conditions.size())) throw std::runtime_error("index out of range"); @@ -647,27 +633,17 @@ namespace CSMWorld { case 0: // Function { - infoSelectWrapper.setFunctionName(static_cast(value.toInt())); - - if (infoSelectWrapper.getComparisonType() != ConstInfoSelectWrapper::Comparison_Numeric - && infoSelectWrapper.getVariant().getType() != ESM::VT_Int) - { - infoSelectWrapper.getVariant().setType(ESM::VT_Int); - } - - infoSelectWrapper.update(); + infoSelectWrapper.setFunctionName(static_cast(value.toInt())); break; } case 1: // Variable { infoSelectWrapper.setVariableName(value.toString().toUtf8().constData()); - infoSelectWrapper.update(); break; } case 2: // Relation { - infoSelectWrapper.setRelationType(static_cast(value.toInt())); - infoSelectWrapper.update(); + infoSelectWrapper.setRelationType(static_cast(value.toInt())); break; } case 3: // Value @@ -679,13 +655,11 @@ namespace CSMWorld // QVariant seems to have issues converting 0 if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) { - infoSelectWrapper.getVariant().setType(ESM::VT_Int); - infoSelectWrapper.getVariant().setInteger(value.toInt()); + infoSelectWrapper.setValue(value.toInt()); } else if (value.toFloat(&conversionResult) && conversionResult) { - infoSelectWrapper.getVariant().setType(ESM::VT_Float); - infoSelectWrapper.getVariant().setFloat(value.toFloat()); + infoSelectWrapper.setValue(value.toFloat()); } break; } @@ -694,8 +668,7 @@ namespace CSMWorld { if ((value.toInt(&conversionResult) && conversionResult) || value.toString().compare("0") == 0) { - infoSelectWrapper.getVariant().setType(ESM::VT_Int); - infoSelectWrapper.getVariant().setInteger(value.toInt()); + infoSelectWrapper.setValue(value.toInt()); } break; } diff --git a/apps/opencs/view/world/idcompletiondelegate.cpp b/apps/opencs/view/world/idcompletiondelegate.cpp index 3e26ed9250..e39be392f4 100644 --- a/apps/opencs/view/world/idcompletiondelegate.cpp +++ b/apps/opencs/view/world/idcompletiondelegate.cpp @@ -46,41 +46,41 @@ QWidget* CSVWorld::IdCompletionDelegate::createEditor(QWidget* parent, const QSt switch (conditionFunction) { - case CSMWorld::ConstInfoSelectWrapper::Function_Global: + case ESM::DialogueCondition::Function_Global: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_GlobalVariable); } - case CSMWorld::ConstInfoSelectWrapper::Function_Journal: + case ESM::DialogueCondition::Function_Journal: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Journal); } - case CSMWorld::ConstInfoSelectWrapper::Function_Item: + case ESM::DialogueCondition::Function_Item: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); } - case CSMWorld::ConstInfoSelectWrapper::Function_Dead: - case CSMWorld::ConstInfoSelectWrapper::Function_NotId: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_NotId: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Referenceable); } - case CSMWorld::ConstInfoSelectWrapper::Function_NotFaction: + case ESM::DialogueCondition::Function_NotFaction: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Faction); } - case CSMWorld::ConstInfoSelectWrapper::Function_NotClass: + case ESM::DialogueCondition::Function_NotClass: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Class); } - case CSMWorld::ConstInfoSelectWrapper::Function_NotRace: + case ESM::DialogueCondition::Function_NotRace: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Race); } - case CSMWorld::ConstInfoSelectWrapper::Function_NotCell: + case ESM::DialogueCondition::Function_NotCell: { return createEditor(parent, option, index, CSMWorld::ColumnBase::Display_Cell); } - case CSMWorld::ConstInfoSelectWrapper::Function_Local: - case CSMWorld::ConstInfoSelectWrapper::Function_NotLocal: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: { return new CSVWidget::DropLineEdit(display, parent); } diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 2693811357..acf87ccc61 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -31,15 +31,15 @@ namespace bool matchesStaticFilters(const MWDialogue::SelectWrapper& select, const MWWorld::Ptr& actor) { const ESM::RefId selectId = select.getId(); - if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotId) + if (select.getFunction() == ESM::DialogueCondition::Function_NotId) return actor.getCellRef().getRefId() != selectId; if (actor.getClass().isNpc()) { - if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotFaction) + if (select.getFunction() == ESM::DialogueCondition::Function_NotFaction) return actor.getClass().getPrimaryFaction(actor) != selectId; - else if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotClass) + else if (select.getFunction() == ESM::DialogueCondition::Function_NotClass) return actor.get()->mBase->mClass != selectId; - else if (select.getFunction() == MWDialogue::SelectWrapper::Function_NotRace) + else if (select.getFunction() == ESM::DialogueCondition::Function_NotRace) return actor.get()->mBase->mRace != selectId; } return true; @@ -47,7 +47,7 @@ namespace bool matchesStaticFilters(const ESM::DialInfo& info, const MWWorld::Ptr& actor) { - for (const ESM::DialInfo::SelectStruct& select : info.mSelects) + for (const auto& select : info.mSelects) { MWDialogue::SelectWrapper wrapper = select; if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Boolean) @@ -62,7 +62,7 @@ namespace } else if (wrapper.getType() == MWDialogue::SelectWrapper::Type_Numeric) { - if (wrapper.getFunction() == MWDialogue::SelectWrapper::Function_Local) + if (wrapper.getFunction() == ESM::DialogueCondition::Function_Local) { const ESM::RefId& scriptName = actor.getClass().getScript(actor); if (scriptName.empty()) @@ -207,9 +207,8 @@ bool MWDialogue::Filter::testPlayer(const ESM::DialInfo& info) const bool MWDialogue::Filter::testSelectStructs(const ESM::DialInfo& info) const { - for (std::vector::const_iterator iter(info.mSelects.begin()); - iter != info.mSelects.end(); ++iter) - if (!testSelectStruct(*iter)) + for (const auto& select : info.mSelects) + if (!testSelectStruct(select)) return false; return true; @@ -270,11 +269,11 @@ bool MWDialogue::Filter::testSelectStruct(const SelectWrapper& select) const // If the actor is a creature, we pass all conditions only applicable to NPCs. return true; - if (select.getFunction() == SelectWrapper::Function_Choice && mChoice == -1) + if (select.getFunction() == ESM::DialogueCondition::Function_Choice && mChoice == -1) // If not currently in a choice, we reject all conditions that test against choices. return false; - if (select.getFunction() == SelectWrapper::Function_Weather + if (select.getFunction() == ESM::DialogueCondition::Function_Weather && !(MWBase::Environment::get().getWorld()->isCellExterior() || MWBase::Environment::get().getWorld()->isCellQuasiExterior())) // Reject weather conditions in interior cells @@ -305,29 +304,31 @@ bool MWDialogue::Filter::testSelectStructNumeric(const SelectWrapper& select) co { switch (select.getFunction()) { - case SelectWrapper::Function_Global: + case ESM::DialogueCondition::Function_Global: // internally all globals are float :( return select.selectCompare(MWBase::Environment::get().getWorld()->getGlobalFloat(select.getName())); - case SelectWrapper::Function_Local: + case ESM::DialogueCondition::Function_Local: { return testFunctionLocal(select); } - case SelectWrapper::Function_NotLocal: + case ESM::DialogueCondition::Function_NotLocal: { return !testFunctionLocal(select); } - case SelectWrapper::Function_PcHealthPercent: + case ESM::DialogueCondition::Function_PcHealthPercent: { MWWorld::Ptr player = MWMechanics::getPlayer(); return select.selectCompare( static_cast(player.getClass().getCreatureStats(player).getHealth().getRatio() * 100)); } - case SelectWrapper::Function_PcDynamicStat: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealth: { MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -336,7 +337,7 @@ bool MWDialogue::Filter::testSelectStructNumeric(const SelectWrapper& select) co return select.selectCompare(value); } - case SelectWrapper::Function_HealthPercent: + case ESM::DialogueCondition::Function_Health_Percent: { return select.selectCompare( static_cast(mActor.getClass().getCreatureStats(mActor).getHealth().getRatio() * 100)); @@ -354,26 +355,29 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons switch (select.getFunction()) { - case SelectWrapper::Function_Journal: + case ESM::DialogueCondition::Function_Journal: return MWBase::Environment::get().getJournal()->getJournalIndex(select.getId()); - case SelectWrapper::Function_Item: + case ESM::DialogueCondition::Function_Item: { MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); return store.count(select.getId()); } - case SelectWrapper::Function_Dead: + case ESM::DialogueCondition::Function_Dead: return MWBase::Environment::get().getMechanicsManager()->countDeaths(select.getId()); - case SelectWrapper::Function_Choice: + case ESM::DialogueCondition::Function_Choice: return mChoice; - case SelectWrapper::Function_AiSetting: + case ESM::DialogueCondition::Function_Fight: + case ESM::DialogueCondition::Function_Hello: + case ESM::DialogueCondition::Function_Alarm: + case ESM::DialogueCondition::Function_Flee: { int argument = select.getArgument(); if (argument < 0 || argument > 3) @@ -386,32 +390,65 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons .getAiSetting(static_cast(argument)) .getModified(false); } - case SelectWrapper::Function_PcAttribute: + case ESM::DialogueCondition::Function_PcStrength: + case ESM::DialogueCondition::Function_PcIntelligence: + case ESM::DialogueCondition::Function_PcWillpower: + case ESM::DialogueCondition::Function_PcAgility: + case ESM::DialogueCondition::Function_PcSpeed: + case ESM::DialogueCondition::Function_PcEndurance: + case ESM::DialogueCondition::Function_PcPersonality: + case ESM::DialogueCondition::Function_PcLuck: { ESM::RefId attribute = ESM::Attribute::indexToRefId(select.getArgument()); return player.getClass().getCreatureStats(player).getAttribute(attribute).getModified(); } - case SelectWrapper::Function_PcSkill: + case ESM::DialogueCondition::Function_PcBlock: + case ESM::DialogueCondition::Function_PcArmorer: + case ESM::DialogueCondition::Function_PcMediumArmor: + case ESM::DialogueCondition::Function_PcHeavyArmor: + case ESM::DialogueCondition::Function_PcBluntWeapon: + case ESM::DialogueCondition::Function_PcLongBlade: + case ESM::DialogueCondition::Function_PcAxe: + case ESM::DialogueCondition::Function_PcSpear: + case ESM::DialogueCondition::Function_PcAthletics: + case ESM::DialogueCondition::Function_PcEnchant: + case ESM::DialogueCondition::Function_PcDestruction: + case ESM::DialogueCondition::Function_PcAlteration: + case ESM::DialogueCondition::Function_PcIllusion: + case ESM::DialogueCondition::Function_PcConjuration: + case ESM::DialogueCondition::Function_PcMysticism: + case ESM::DialogueCondition::Function_PcRestoration: + case ESM::DialogueCondition::Function_PcAlchemy: + case ESM::DialogueCondition::Function_PcUnarmored: + case ESM::DialogueCondition::Function_PcSecurity: + case ESM::DialogueCondition::Function_PcSneak: + case ESM::DialogueCondition::Function_PcAcrobatics: + case ESM::DialogueCondition::Function_PcLightArmor: + case ESM::DialogueCondition::Function_PcShortBlade: + case ESM::DialogueCondition::Function_PcMarksman: + case ESM::DialogueCondition::Function_PcMerchantile: + case ESM::DialogueCondition::Function_PcSpeechcraft: + case ESM::DialogueCondition::Function_PcHandToHand: { ESM::RefId skill = ESM::Skill::indexToRefId(select.getArgument()); return static_cast(player.getClass().getNpcStats(player).getSkill(skill).getModified()); } - case SelectWrapper::Function_FriendlyHit: + case ESM::DialogueCondition::Function_FriendHit: { int hits = mActor.getClass().getCreatureStats(mActor).getFriendlyHits(); return hits > 4 ? 4 : hits; } - case SelectWrapper::Function_PcLevel: + case ESM::DialogueCondition::Function_PcLevel: return player.getClass().getCreatureStats(player).getLevel(); - case SelectWrapper::Function_PcGender: + case ESM::DialogueCondition::Function_PcGender: return player.get()->mBase->isMale() ? 0 : 1; - case SelectWrapper::Function_PcClothingModifier: + case ESM::DialogueCondition::Function_PcClothingModifier: { const MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); @@ -428,11 +465,11 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons return value; } - case SelectWrapper::Function_PcCrimeLevel: + case ESM::DialogueCondition::Function_PcCrimeLevel: return player.getClass().getNpcStats(player).getBounty(); - case SelectWrapper::Function_RankRequirement: + case ESM::DialogueCondition::Function_RankRequirement: { const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); if (faction.empty()) @@ -454,23 +491,23 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons return result; } - case SelectWrapper::Function_Level: + case ESM::DialogueCondition::Function_Level: return mActor.getClass().getCreatureStats(mActor).getLevel(); - case SelectWrapper::Function_PCReputation: + case ESM::DialogueCondition::Function_PcReputation: return player.getClass().getNpcStats(player).getReputation(); - case SelectWrapper::Function_Weather: + case ESM::DialogueCondition::Function_Weather: return MWBase::Environment::get().getWorld()->getCurrentWeather(); - case SelectWrapper::Function_Reputation: + case ESM::DialogueCondition::Function_Reputation: return mActor.getClass().getNpcStats(mActor).getReputation(); - case SelectWrapper::Function_FactionRankDiff: + case ESM::DialogueCondition::Function_FactionRankDifference: { const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); @@ -482,14 +519,14 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons return rank - npcRank; } - case SelectWrapper::Function_WerewolfKills: + case ESM::DialogueCondition::Function_PcWerewolfKills: return player.getClass().getNpcStats(player).getWerewolfKills(); - case SelectWrapper::Function_RankLow: - case SelectWrapper::Function_RankHigh: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: { - bool low = select.getFunction() == SelectWrapper::Function_RankLow; + bool low = select.getFunction() == ESM::DialogueCondition::Function_FacReactionLowest; const ESM::RefId& factionId = mActor.getClass().getPrimaryFaction(mActor); @@ -512,7 +549,7 @@ int MWDialogue::Filter::getSelectStructInteger(const SelectWrapper& select) cons return value; } - case SelectWrapper::Function_CreatureTargetted: + case ESM::DialogueCondition::Function_CreatureTarget: { MWWorld::Ptr target; @@ -539,53 +576,49 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con switch (select.getFunction()) { - case SelectWrapper::Function_False: - - return false; - - case SelectWrapper::Function_NotId: + case ESM::DialogueCondition::Function_NotId: return mActor.getCellRef().getRefId() != select.getId(); - case SelectWrapper::Function_NotFaction: + case ESM::DialogueCondition::Function_NotFaction: return mActor.getClass().getPrimaryFaction(mActor) != select.getId(); - case SelectWrapper::Function_NotClass: + case ESM::DialogueCondition::Function_NotClass: return mActor.get()->mBase->mClass != select.getId(); - case SelectWrapper::Function_NotRace: + case ESM::DialogueCondition::Function_NotRace: return mActor.get()->mBase->mRace != select.getId(); - case SelectWrapper::Function_NotCell: + case ESM::DialogueCondition::Function_NotCell: { std::string_view actorCell = MWBase::Environment::get().getWorld()->getCellName(mActor.getCell()); return !Misc::StringUtils::ciStartsWith(actorCell, select.getCellName()); } - case SelectWrapper::Function_SameGender: + case ESM::DialogueCondition::Function_SameSex: return (player.get()->mBase->mFlags & ESM::NPC::Female) == (mActor.get()->mBase->mFlags & ESM::NPC::Female); - case SelectWrapper::Function_SameRace: + case ESM::DialogueCondition::Function_SameRace: return mActor.get()->mBase->mRace == player.get()->mBase->mRace; - case SelectWrapper::Function_SameFaction: + case ESM::DialogueCondition::Function_SameFaction: return player.getClass().getNpcStats(player).isInFaction(mActor.getClass().getPrimaryFaction(mActor)); - case SelectWrapper::Function_PcCommonDisease: + case ESM::DialogueCondition::Function_PcCommonDisease: return player.getClass().getCreatureStats(player).hasCommonDisease(); - case SelectWrapper::Function_PcBlightDisease: + case ESM::DialogueCondition::Function_PcBlightDisease: return player.getClass().getCreatureStats(player).hasBlightDisease(); - case SelectWrapper::Function_PcCorprus: + case ESM::DialogueCondition::Function_PcCorpus: return player.getClass() .getCreatureStats(player) @@ -594,7 +627,7 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con .getMagnitude() != 0; - case SelectWrapper::Function_PcExpelled: + case ESM::DialogueCondition::Function_PcExpelled: { const ESM::RefId& faction = mActor.getClass().getPrimaryFaction(mActor); @@ -604,7 +637,7 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con return player.getClass().getNpcStats(player).getExpelled(faction); } - case SelectWrapper::Function_PcVampire: + case ESM::DialogueCondition::Function_PcVampire: return player.getClass() .getCreatureStats(player) @@ -613,27 +646,27 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con .getMagnitude() > 0; - case SelectWrapper::Function_TalkedToPc: + case ESM::DialogueCondition::Function_TalkedToPc: return mTalkedToPlayer; - case SelectWrapper::Function_Alarmed: + case ESM::DialogueCondition::Function_Alarmed: return mActor.getClass().getCreatureStats(mActor).isAlarmed(); - case SelectWrapper::Function_Detected: + case ESM::DialogueCondition::Function_Detected: return MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, mActor); - case SelectWrapper::Function_Attacked: + case ESM::DialogueCondition::Function_Attacked: return mActor.getClass().getCreatureStats(mActor).getAttacked(); - case SelectWrapper::Function_ShouldAttack: + case ESM::DialogueCondition::Function_ShouldAttack: return MWBase::Environment::get().getMechanicsManager()->isAggressive(mActor, MWMechanics::getPlayer()); - case SelectWrapper::Function_Werewolf: + case ESM::DialogueCondition::Function_Werewolf: return mActor.getClass().getNpcStats(mActor).isWerewolf(); diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index 0cee8bb009..cc07ec8709 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -10,21 +10,21 @@ namespace { template - bool selectCompareImp(char comp, T1 value1, T2 value2) + bool selectCompareImp(ESM::DialogueCondition::Comparison comp, T1 value1, T2 value2) { switch (comp) { - case '0': + case ESM::DialogueCondition::Comp_Eq: return value1 == value2; - case '1': + case ESM::DialogueCondition::Comp_Ne: return value1 != value2; - case '2': + case ESM::DialogueCondition::Comp_Gt: return value1 > value2; - case '3': + case ESM::DialogueCondition::Comp_Ge: return value1 >= value2; - case '4': + case ESM::DialogueCondition::Comp_Ls: return value1 < value2; - case '5': + case ESM::DialogueCondition::Comp_Le: return value1 <= value2; } @@ -32,409 +32,242 @@ namespace } template - bool selectCompareImp(const ESM::DialInfo::SelectStruct& select, T value1) + bool selectCompareImp(const ESM::DialogueCondition& select, T value1) { - if (select.mValue.getType() == ESM::VT_Int) - { - return selectCompareImp(select.mSelectRule[4], value1, select.mValue.getInteger()); - } - else if (select.mValue.getType() == ESM::VT_Float) - { - return selectCompareImp(select.mSelectRule[4], value1, select.mValue.getFloat()); - } - else - throw std::runtime_error("unsupported variable type in dialogue info select"); + return std::visit( + [&](auto value) { return selectCompareImp(select.mComparison, value1, value); }, select.mValue); } } -MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::decodeFunction() const -{ - const int index = Misc::StringUtils::toNumeric(mSelect.mSelectRule.substr(2, 2), 0); - - switch (index) - { - case 0: - return Function_RankLow; - case 1: - return Function_RankHigh; - case 2: - return Function_RankRequirement; - case 3: - return Function_Reputation; - case 4: - return Function_HealthPercent; - case 5: - return Function_PCReputation; - case 6: - return Function_PcLevel; - case 7: - return Function_PcHealthPercent; - case 8: - case 9: - return Function_PcDynamicStat; - case 10: - return Function_PcAttribute; - case 11: - case 12: - case 13: - case 14: - case 15: - case 16: - case 17: - case 18: - case 19: - case 20: - case 21: - case 22: - case 23: - case 24: - case 25: - case 26: - case 27: - case 28: - case 29: - case 30: - case 31: - case 32: - case 33: - case 34: - case 35: - case 36: - case 37: - return Function_PcSkill; - case 38: - return Function_PcGender; - case 39: - return Function_PcExpelled; - case 40: - return Function_PcCommonDisease; - case 41: - return Function_PcBlightDisease; - case 42: - return Function_PcClothingModifier; - case 43: - return Function_PcCrimeLevel; - case 44: - return Function_SameGender; - case 45: - return Function_SameRace; - case 46: - return Function_SameFaction; - case 47: - return Function_FactionRankDiff; - case 48: - return Function_Detected; - case 49: - return Function_Alarmed; - case 50: - return Function_Choice; - case 51: - case 52: - case 53: - case 54: - case 55: - case 56: - case 57: - return Function_PcAttribute; - case 58: - return Function_PcCorprus; - case 59: - return Function_Weather; - case 60: - return Function_PcVampire; - case 61: - return Function_Level; - case 62: - return Function_Attacked; - case 63: - return Function_TalkedToPc; - case 64: - return Function_PcDynamicStat; - case 65: - return Function_CreatureTargetted; - case 66: - return Function_FriendlyHit; - case 67: - case 68: - case 69: - case 70: - return Function_AiSetting; - case 71: - return Function_ShouldAttack; - case 72: - return Function_Werewolf; - case 73: - return Function_WerewolfKills; - } - - return Function_False; -} - -MWDialogue::SelectWrapper::SelectWrapper(const ESM::DialInfo::SelectStruct& select) +MWDialogue::SelectWrapper::SelectWrapper(const ESM::DialogueCondition& select) : mSelect(select) { } -MWDialogue::SelectWrapper::Function MWDialogue::SelectWrapper::getFunction() const +ESM::DialogueCondition::Function MWDialogue::SelectWrapper::getFunction() const { - char type = mSelect.mSelectRule[1]; - - switch (type) - { - case '1': - return decodeFunction(); - case '2': - return Function_Global; - case '3': - return Function_Local; - case '4': - return Function_Journal; - case '5': - return Function_Item; - case '6': - return Function_Dead; - case '7': - return Function_NotId; - case '8': - return Function_NotFaction; - case '9': - return Function_NotClass; - case 'A': - return Function_NotRace; - case 'B': - return Function_NotCell; - case 'C': - return Function_NotLocal; - } - - return Function_None; + return mSelect.mFunction; } int MWDialogue::SelectWrapper::getArgument() const { - if (mSelect.mSelectRule[1] != '1') - return 0; - - int index = 0; - - std::istringstream(mSelect.mSelectRule.substr(2, 2)) >> index; - - switch (index) + switch (mSelect.mFunction) { // AI settings - case 67: + case ESM::DialogueCondition::Function_Fight: return 1; - case 68: + case ESM::DialogueCondition::Function_Hello: return 0; - case 69: + case ESM::DialogueCondition::Function_Alarm: return 3; - case 70: + case ESM::DialogueCondition::Function_Flee: return 2; // attributes - case 10: + case ESM::DialogueCondition::Function_PcStrength: return 0; - case 51: + case ESM::DialogueCondition::Function_PcIntelligence: return 1; - case 52: + case ESM::DialogueCondition::Function_PcWillpower: return 2; - case 53: + case ESM::DialogueCondition::Function_PcAgility: return 3; - case 54: + case ESM::DialogueCondition::Function_PcSpeed: return 4; - case 55: + case ESM::DialogueCondition::Function_PcEndurance: return 5; - case 56: + case ESM::DialogueCondition::Function_PcPersonality: return 6; - case 57: + case ESM::DialogueCondition::Function_PcLuck: return 7; // skills - case 11: + case ESM::DialogueCondition::Function_PcBlock: return 0; - case 12: + case ESM::DialogueCondition::Function_PcArmorer: return 1; - case 13: + case ESM::DialogueCondition::Function_PcMediumArmor: return 2; - case 14: + case ESM::DialogueCondition::Function_PcHeavyArmor: return 3; - case 15: + case ESM::DialogueCondition::Function_PcBluntWeapon: return 4; - case 16: + case ESM::DialogueCondition::Function_PcLongBlade: return 5; - case 17: + case ESM::DialogueCondition::Function_PcAxe: return 6; - case 18: + case ESM::DialogueCondition::Function_PcSpear: return 7; - case 19: + case ESM::DialogueCondition::Function_PcAthletics: return 8; - case 20: + case ESM::DialogueCondition::Function_PcEnchant: return 9; - case 21: + case ESM::DialogueCondition::Function_PcDestruction: return 10; - case 22: + case ESM::DialogueCondition::Function_PcAlteration: return 11; - case 23: + case ESM::DialogueCondition::Function_PcIllusion: return 12; - case 24: + case ESM::DialogueCondition::Function_PcConjuration: return 13; - case 25: + case ESM::DialogueCondition::Function_PcMysticism: return 14; - case 26: + case ESM::DialogueCondition::Function_PcRestoration: return 15; - case 27: + case ESM::DialogueCondition::Function_PcAlchemy: return 16; - case 28: + case ESM::DialogueCondition::Function_PcUnarmored: return 17; - case 29: + case ESM::DialogueCondition::Function_PcSecurity: return 18; - case 30: + case ESM::DialogueCondition::Function_PcSneak: return 19; - case 31: + case ESM::DialogueCondition::Function_PcAcrobatics: return 20; - case 32: + case ESM::DialogueCondition::Function_PcLightArmor: return 21; - case 33: + case ESM::DialogueCondition::Function_PcShortBlade: return 22; - case 34: + case ESM::DialogueCondition::Function_PcMarksman: return 23; - case 35: + case ESM::DialogueCondition::Function_PcMerchantile: return 24; - case 36: + case ESM::DialogueCondition::Function_PcSpeechcraft: return 25; - case 37: + case ESM::DialogueCondition::Function_PcHandToHand: return 26; // dynamic stats - case 8: + case ESM::DialogueCondition::Function_PcMagicka: return 1; - case 9: + case ESM::DialogueCondition::Function_PcFatigue: return 2; - case 64: + case ESM::DialogueCondition::Function_PcHealth: + return 0; + default: return 0; } - - return 0; } MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const { - static const Function integerFunctions[] = { - Function_Journal, - Function_Item, - Function_Dead, - Function_Choice, - Function_AiSetting, - Function_PcAttribute, - Function_PcSkill, - Function_FriendlyHit, - Function_PcLevel, - Function_PcGender, - Function_PcClothingModifier, - Function_PcCrimeLevel, - Function_RankRequirement, - Function_Level, - Function_PCReputation, - Function_Weather, - Function_Reputation, - Function_FactionRankDiff, - Function_WerewolfKills, - Function_RankLow, - Function_RankHigh, - Function_CreatureTargetted, - // end marker - Function_None, - }; - - static const Function numericFunctions[] = { - Function_Global, - Function_Local, - Function_NotLocal, - Function_PcDynamicStat, - Function_PcHealthPercent, - Function_HealthPercent, - // end marker - Function_None, - }; - - static const Function booleanFunctions[] = { - Function_False, - Function_SameGender, - Function_SameRace, - Function_SameFaction, - Function_PcCommonDisease, - Function_PcBlightDisease, - Function_PcCorprus, - Function_PcExpelled, - Function_PcVampire, - Function_TalkedToPc, - Function_Alarmed, - Function_Detected, - Function_Attacked, - Function_ShouldAttack, - Function_Werewolf, - // end marker - Function_None, - }; - - static const Function invertedBooleanFunctions[] = { - Function_NotId, - Function_NotFaction, - Function_NotClass, - Function_NotRace, - Function_NotCell, - // end marker - Function_None, - }; - - Function function = getFunction(); - - for (int i = 0; integerFunctions[i] != Function_None; ++i) - if (integerFunctions[i] == function) + switch (mSelect.mFunction) + { + case ESM::DialogueCondition::Function_Journal: + case ESM::DialogueCondition::Function_Item: + case ESM::DialogueCondition::Function_Dead: + case ESM::DialogueCondition::Function_Choice: + case ESM::DialogueCondition::Function_Fight: + case ESM::DialogueCondition::Function_Hello: + case ESM::DialogueCondition::Function_Alarm: + case ESM::DialogueCondition::Function_Flee: + case ESM::DialogueCondition::Function_PcStrength: + case ESM::DialogueCondition::Function_PcIntelligence: + case ESM::DialogueCondition::Function_PcWillpower: + case ESM::DialogueCondition::Function_PcAgility: + case ESM::DialogueCondition::Function_PcSpeed: + case ESM::DialogueCondition::Function_PcEndurance: + case ESM::DialogueCondition::Function_PcPersonality: + case ESM::DialogueCondition::Function_PcLuck: + case ESM::DialogueCondition::Function_PcBlock: + case ESM::DialogueCondition::Function_PcArmorer: + case ESM::DialogueCondition::Function_PcMediumArmor: + case ESM::DialogueCondition::Function_PcHeavyArmor: + case ESM::DialogueCondition::Function_PcBluntWeapon: + case ESM::DialogueCondition::Function_PcLongBlade: + case ESM::DialogueCondition::Function_PcAxe: + case ESM::DialogueCondition::Function_PcSpear: + case ESM::DialogueCondition::Function_PcAthletics: + case ESM::DialogueCondition::Function_PcEnchant: + case ESM::DialogueCondition::Function_PcDestruction: + case ESM::DialogueCondition::Function_PcAlteration: + case ESM::DialogueCondition::Function_PcIllusion: + case ESM::DialogueCondition::Function_PcConjuration: + case ESM::DialogueCondition::Function_PcMysticism: + case ESM::DialogueCondition::Function_PcRestoration: + case ESM::DialogueCondition::Function_PcAlchemy: + case ESM::DialogueCondition::Function_PcUnarmored: + case ESM::DialogueCondition::Function_PcSecurity: + case ESM::DialogueCondition::Function_PcSneak: + case ESM::DialogueCondition::Function_PcAcrobatics: + case ESM::DialogueCondition::Function_PcLightArmor: + case ESM::DialogueCondition::Function_PcShortBlade: + case ESM::DialogueCondition::Function_PcMarksman: + case ESM::DialogueCondition::Function_PcMerchantile: + case ESM::DialogueCondition::Function_PcSpeechcraft: + case ESM::DialogueCondition::Function_PcHandToHand: + case ESM::DialogueCondition::Function_FriendHit: + case ESM::DialogueCondition::Function_PcLevel: + case ESM::DialogueCondition::Function_PcGender: + case ESM::DialogueCondition::Function_PcClothingModifier: + case ESM::DialogueCondition::Function_PcCrimeLevel: + case ESM::DialogueCondition::Function_RankRequirement: + case ESM::DialogueCondition::Function_Level: + case ESM::DialogueCondition::Function_PcReputation: + case ESM::DialogueCondition::Function_Weather: + case ESM::DialogueCondition::Function_Reputation: + case ESM::DialogueCondition::Function_FactionRankDifference: + case ESM::DialogueCondition::Function_PcWerewolfKills: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: + case ESM::DialogueCondition::Function_CreatureTarget: return Type_Integer; - - for (int i = 0; numericFunctions[i] != Function_None; ++i) - if (numericFunctions[i] == function) + case ESM::DialogueCondition::Function_Global: + case ESM::DialogueCondition::Function_Local: + case ESM::DialogueCondition::Function_NotLocal: + case ESM::DialogueCondition::Function_PcHealth: + case ESM::DialogueCondition::Function_PcMagicka: + case ESM::DialogueCondition::Function_PcFatigue: + case ESM::DialogueCondition::Function_PcHealthPercent: + case ESM::DialogueCondition::Function_Health_Percent: return Type_Numeric; - - for (int i = 0; booleanFunctions[i] != Function_None; ++i) - if (booleanFunctions[i] == function) + case ESM::DialogueCondition::Function_SameSex: + case ESM::DialogueCondition::Function_SameRace: + case ESM::DialogueCondition::Function_SameFaction: + case ESM::DialogueCondition::Function_PcCommonDisease: + case ESM::DialogueCondition::Function_PcBlightDisease: + case ESM::DialogueCondition::Function_PcCorpus: + case ESM::DialogueCondition::Function_PcExpelled: + case ESM::DialogueCondition::Function_PcVampire: + case ESM::DialogueCondition::Function_TalkedToPc: + case ESM::DialogueCondition::Function_Alarmed: + case ESM::DialogueCondition::Function_Detected: + case ESM::DialogueCondition::Function_Attacked: + case ESM::DialogueCondition::Function_ShouldAttack: + case ESM::DialogueCondition::Function_Werewolf: return Type_Boolean; - - for (int i = 0; invertedBooleanFunctions[i] != Function_None; ++i) - if (invertedBooleanFunctions[i] == function) + case ESM::DialogueCondition::Function_NotId: + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_NotCell: return Type_Inverted; - - return Type_None; + default: + return Type_None; + }; } bool MWDialogue::SelectWrapper::isNpcOnly() const { - static const Function functions[] = { - Function_NotFaction, - Function_NotClass, - Function_NotRace, - Function_SameGender, - Function_SameRace, - Function_SameFaction, - Function_RankRequirement, - Function_Reputation, - Function_FactionRankDiff, - Function_Werewolf, - Function_WerewolfKills, - Function_RankLow, - Function_RankHigh, - // end marker - Function_None, - }; - - Function function = getFunction(); - - for (int i = 0; functions[i] != Function_None; ++i) - if (functions[i] == function) + switch (mSelect.mFunction) + { + case ESM::DialogueCondition::Function_NotFaction: + case ESM::DialogueCondition::Function_NotClass: + case ESM::DialogueCondition::Function_NotRace: + case ESM::DialogueCondition::Function_SameSex: + case ESM::DialogueCondition::Function_SameRace: + case ESM::DialogueCondition::Function_SameFaction: + case ESM::DialogueCondition::Function_RankRequirement: + case ESM::DialogueCondition::Function_Reputation: + case ESM::DialogueCondition::Function_FactionRankDifference: + case ESM::DialogueCondition::Function_Werewolf: + case ESM::DialogueCondition::Function_PcWerewolfKills: + case ESM::DialogueCondition::Function_FacReactionLowest: + case ESM::DialogueCondition::Function_FacReactionHighest: return true; - - return false; + default: + return false; + } } bool MWDialogue::SelectWrapper::selectCompare(int value) const @@ -454,15 +287,15 @@ bool MWDialogue::SelectWrapper::selectCompare(bool value) const std::string MWDialogue::SelectWrapper::getName() const { - return Misc::StringUtils::lowerCase(getCellName()); + return Misc::StringUtils::lowerCase(mSelect.mVariable); } std::string_view MWDialogue::SelectWrapper::getCellName() const { - return std::string_view(mSelect.mSelectRule).substr(5); + return mSelect.mVariable; } ESM::RefId MWDialogue::SelectWrapper::getId() const { - return ESM::RefId::stringRefId(getCellName()); + return ESM::RefId::stringRefId(mSelect.mVariable); } diff --git a/apps/openmw/mwdialogue/selectwrapper.hpp b/apps/openmw/mwdialogue/selectwrapper.hpp index f736b504d8..d831b6cea0 100644 --- a/apps/openmw/mwdialogue/selectwrapper.hpp +++ b/apps/openmw/mwdialogue/selectwrapper.hpp @@ -7,62 +7,9 @@ namespace MWDialogue { class SelectWrapper { - const ESM::DialInfo::SelectStruct& mSelect; + const ESM::DialogueCondition& mSelect; public: - enum Function - { - Function_None, - Function_False, - Function_Journal, - Function_Item, - Function_Dead, - Function_NotId, - Function_NotFaction, - Function_NotClass, - Function_NotRace, - Function_NotCell, - Function_NotLocal, - Function_Local, - Function_Global, - Function_SameGender, - Function_SameRace, - Function_SameFaction, - Function_Choice, - Function_PcCommonDisease, - Function_PcBlightDisease, - Function_PcCorprus, - Function_AiSetting, - Function_PcAttribute, - Function_PcSkill, - Function_PcExpelled, - Function_PcVampire, - Function_FriendlyHit, - Function_TalkedToPc, - Function_PcLevel, - Function_PcHealthPercent, - Function_PcDynamicStat, - Function_PcGender, - Function_PcClothingModifier, - Function_PcCrimeLevel, - Function_RankRequirement, - Function_HealthPercent, - Function_Level, - Function_PCReputation, - Function_Weather, - Function_Reputation, - Function_Alarmed, - Function_FactionRankDiff, - Function_Detected, - Function_Attacked, - Function_ShouldAttack, - Function_CreatureTargetted, - Function_Werewolf, - Function_WerewolfKills, - Function_RankLow, - Function_RankHigh - }; - enum Type { Type_None, @@ -72,13 +19,10 @@ namespace MWDialogue Type_Inverted }; - private: - Function decodeFunction() const; - public: - SelectWrapper(const ESM::DialInfo::SelectStruct& select); + SelectWrapper(const ESM::DialogueCondition& select); - Function getFunction() const; + ESM::DialogueCondition::Function getFunction() const; int getArgument() const; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7ac01ef169..68411be2fc 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -181,7 +181,7 @@ add_component_dir (esm3 inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache - infoorder timestamp formatversion landrecorddata selectiongroup + infoorder timestamp formatversion landrecorddata selectiongroup dialoguecondition ) add_component_dir (esmterrain diff --git a/components/esm3/dialoguecondition.cpp b/components/esm3/dialoguecondition.cpp new file mode 100644 index 0000000000..ba8f9586ce --- /dev/null +++ b/components/esm3/dialoguecondition.cpp @@ -0,0 +1,204 @@ +#include "dialoguecondition.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" +#include "variant.hpp" + +#include +#include +#include + +namespace ESM +{ + std::optional DialogueCondition::load(ESMReader& esm, ESM::RefId context) + { + std::string rule = esm.getHString(); + ESM::Variant variant; + variant.read(esm, Variant::Format_Info); + if (rule.size() < 5) + { + Log(Debug::Warning) << "Found invalid SCVR rule of size " << rule.size() << " in INFO " << context; + return {}; + } + if (rule[4] < '0' || rule[4] > '5') + { + Log(Debug::Warning) << "Found invalid SCVR comparison operator " << static_cast(rule[4]) << " in INFO " + << context; + return {}; + } + DialogueCondition condition; + if (rule[0] >= '0' && rule[0] <= '9') + condition.mIndex = rule[0] - '0'; + else + { + Log(Debug::Info) << "Found invalid SCVR index " << static_cast(rule[0]) << " in INFO " << context; + condition.mIndex = 0; + } + if (rule[1] == '1') + { + int function = Misc::StringUtils::toNumeric(std::string_view{ rule }.substr(2, 2), -1); + if (function >= Function_FacReactionLowest && function <= Function_PcWerewolfKills) + condition.mFunction = static_cast(function); + else + { + Log(Debug::Warning) << "Encountered invalid SCVR function index " << function << " in INFO " << context; + return {}; + } + } + else if (rule[1] > '1' && rule[1] <= '9' || rule[1] >= 'A' && rule[1] <= 'C') + { + if (rule.size() == 5) + { + Log(Debug::Warning) << "Missing variable for SCVR of type " << rule[1] << " in INFO " << context; + return {}; + } + bool malformed = rule[3] != 'X'; + if (rule[1] == '2') + { + condition.mFunction = Function_Global; + malformed |= rule[2] != 'f' && rule[2] != 'l' && rule[2] != 's'; + } + else if (rule[1] == '3') + { + condition.mFunction = Function_Local; + malformed |= rule[2] != 'f' && rule[2] != 'l' && rule[2] != 's'; + } + else if (rule[1] == '4') + { + condition.mFunction = Function_Journal; + malformed |= rule[2] != 'J'; + } + else if (rule[1] == '5') + { + condition.mFunction = Function_Item; + malformed |= rule[2] != 'I'; + } + else if (rule[1] == '6') + { + condition.mFunction = Function_Dead; + malformed |= rule[2] != 'D'; + } + else if (rule[1] == '7') + { + condition.mFunction = Function_NotId; + malformed |= rule[2] != 'X'; + } + else if (rule[1] == '8') + { + condition.mFunction = Function_NotFaction; + malformed |= rule[2] != 'F'; + } + else if (rule[1] == '9') + { + condition.mFunction = Function_NotClass; + malformed |= rule[2] != 'C'; + } + else if (rule[1] == 'A') + { + condition.mFunction = Function_NotRace; + malformed |= rule[2] != 'R'; + } + else if (rule[1] == 'B') + { + condition.mFunction = Function_NotCell; + malformed |= rule[2] != 'L'; + } + else if (rule[1] == 'C') + { + condition.mFunction = Function_NotLocal; + malformed |= rule[2] != 'f' && rule[2] != 'l' && rule[2] != 's'; + } + if (malformed) + Log(Debug::Info) << "Found malformed SCVR rule in INFO " << context; + } + else + { + Log(Debug::Warning) << "Found invalid SCVR function " << static_cast(rule[1]) << " in INFO " + << context; + return {}; + } + condition.mComparison = static_cast(rule[4]); + condition.mVariable = rule.substr(5); + if (variant.getType() == VT_Int) + condition.mValue = variant.getInteger(); + else if (variant.getType() == VT_Float) + condition.mValue = variant.getFloat(); + else + { + Log(Debug::Warning) << "Found invalid SCVR variant " << variant.getType() << " in INFO " << context; + return {}; + } + return condition; + } + + void DialogueCondition::save(ESMWriter& esm) const + { + auto variant = std::visit([](auto value) { return ESM::Variant(value); }, mValue); + std::string rule; + rule.reserve(5 + mVariable.size()); + rule += static_cast(mIndex + '0'); + const auto appendVariableType = [&]() { + if (variant.getType() == VT_Float) + rule += "fX"; + else + { + int32_t value = variant.getInteger(); + if (static_cast(value) == value) + rule += "sX"; + else + rule += "lX"; + } + }; + if (mFunction == Function_Global) + { + rule += '2'; + appendVariableType(); + } + else if (mFunction == Function_Local) + { + rule += '3'; + appendVariableType(); + } + else if (mFunction == Function_Journal) + rule += "4JX"; + else if (mFunction == Function_Item) + rule += "5IX"; + else if (mFunction == Function_Dead) + rule += "6DX"; + else if (mFunction == Function_NotId) + rule += "7XX"; + else if (mFunction == Function_NotFaction) + rule += "8FX"; + else if (mFunction == Function_NotClass) + rule += "9CX"; + else if (mFunction == Function_NotRace) + rule += "ARX"; + else if (mFunction == Function_NotCell) + rule += "BLX"; + else if (mFunction == Function_NotLocal) + { + rule += 'C'; + appendVariableType(); + } + else + { + rule += "100"; + char* start = rule.data() + rule.size(); + char* end = start; + if (mFunction < Function_PcStrength) + start--; + else + start -= 2; + auto result = std::to_chars(start, end, mFunction); + if (result.ec != std::errc()) + { + Log(Debug::Error) << "Failed to save SCVR rule"; + return; + } + } + rule += static_cast(mComparison); + rule += mVariable; + esm.writeHNString("SCVR", rule); + variant.write(esm, Variant::Format_Info); + } +} diff --git a/components/esm3/dialoguecondition.hpp b/components/esm3/dialoguecondition.hpp new file mode 100644 index 0000000000..15ad4944f5 --- /dev/null +++ b/components/esm3/dialoguecondition.hpp @@ -0,0 +1,134 @@ +#ifndef OPENMW_ESM3_DIALOGUECONDITION_H +#define OPENMW_ESM3_DIALOGUECONDITION_H + +#include +#include +#include +#include + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + struct DialogueCondition + { + enum Function : std::int8_t + { + Function_FacReactionLowest = 0, + Function_FacReactionHighest, + Function_RankRequirement, + Function_Reputation, + Function_Health_Percent, + Function_PcReputation, + Function_PcLevel, + Function_PcHealthPercent, + Function_PcMagicka, + Function_PcFatigue, + Function_PcStrength, + Function_PcBlock, + Function_PcArmorer, + Function_PcMediumArmor, + Function_PcHeavyArmor, + Function_PcBluntWeapon, + Function_PcLongBlade, + Function_PcAxe, + Function_PcSpear, + Function_PcAthletics, + Function_PcEnchant, + Function_PcDestruction, + Function_PcAlteration, + Function_PcIllusion, + Function_PcConjuration, + Function_PcMysticism, + Function_PcRestoration, + Function_PcAlchemy, + Function_PcUnarmored, + Function_PcSecurity, + Function_PcSneak, + Function_PcAcrobatics, + Function_PcLightArmor, + Function_PcShortBlade, + Function_PcMarksman, + Function_PcMerchantile, + Function_PcSpeechcraft, + Function_PcHandToHand, + Function_PcGender, + Function_PcExpelled, + Function_PcCommonDisease, + Function_PcBlightDisease, + Function_PcClothingModifier, + Function_PcCrimeLevel, + Function_SameSex, + Function_SameRace, + Function_SameFaction, + Function_FactionRankDifference, + Function_Detected, + Function_Alarmed, + Function_Choice, + Function_PcIntelligence, + Function_PcWillpower, + Function_PcAgility, + Function_PcSpeed, + Function_PcEndurance, + Function_PcPersonality, + Function_PcLuck, + Function_PcCorpus, + Function_Weather, + Function_PcVampire, + Function_Level, + Function_Attacked, + Function_TalkedToPc, + Function_PcHealth, + Function_CreatureTarget, + Function_FriendHit, + Function_Fight, + Function_Hello, + Function_Alarm, + Function_Flee, + Function_ShouldAttack, + Function_Werewolf, + Function_PcWerewolfKills = 73, + + Function_Global, + Function_Local, + Function_Journal, + Function_Item, + Function_Dead, + Function_NotId, + Function_NotFaction, + Function_NotClass, + Function_NotRace, + Function_NotCell, + Function_NotLocal, + + Function_None, // Editor only + }; + + enum Comparison : char + { + Comp_Eq = '0', + Comp_Ne = '1', + Comp_Gt = '2', + Comp_Ge = '3', + Comp_Ls = '4', + Comp_Le = '5', + + Comp_None = ' ', // Editor only + }; + + std::string mVariable; + std::variant mValue = 0; + std::uint8_t mIndex = 0; + Function mFunction = Function_None; + Comparison mComparison = Comp_None; + + static std::optional load(ESMReader& esm, ESM::RefId context); + + void save(ESMWriter& esm) const; + }; +} + +#endif diff --git a/components/esm3/loadinfo.cpp b/components/esm3/loadinfo.cpp index b091edd3f6..8b1147ed45 100644 --- a/components/esm3/loadinfo.cpp +++ b/components/esm3/loadinfo.cpp @@ -3,65 +3,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -#include #include -#include - -namespace -{ - enum class SelectRuleStatus - { - Valid, - Invalid, - Ignorable - }; - - SelectRuleStatus isValidSelectRule(std::string_view rule) - { - if (rule.size() < 5) - return SelectRuleStatus::Invalid; - if (rule[4] < '0' || rule[4] > '5') // Comparison operators - return SelectRuleStatus::Invalid; - if (rule[1] == '1') // Function - { - int function = Misc::StringUtils::toNumeric(rule.substr(2, 2), -1); - if (function >= 0 && function <= 73) - return SelectRuleStatus::Valid; - return SelectRuleStatus::Invalid; - } - if (rule.size() == 5) // Missing ID - return SelectRuleStatus::Invalid; - if (rule[3] != 'X') - return SelectRuleStatus::Ignorable; - constexpr auto ignorable - = [](bool valid) { return valid ? SelectRuleStatus::Valid : SelectRuleStatus::Ignorable; }; - switch (rule[1]) - { - case '2': - case '3': - case 'C': - return ignorable(rule[2] == 's' || rule[2] == 'l' || rule[2] == 'f'); - case '4': - return ignorable(rule[2] == 'J'); - case '5': - return ignorable(rule[2] == 'I'); - case '6': - return ignorable(rule[2] == 'D'); - case '7': - return ignorable(rule[2] == 'X'); - case '8': - return ignorable(rule[2] == 'F'); - case '9': - return ignorable(rule[2] == 'C'); - case 'A': - return ignorable(rule[2] == 'R'); - case 'B': - return ignorable(rule[2] == 'L'); - default: - return SelectRuleStatus::Invalid; - } - } -} namespace ESM { @@ -124,21 +66,9 @@ namespace ESM break; case fourCC("SCVR"): { - SelectStruct ss; - ss.mSelectRule = esm.getHString(); - ss.mValue.read(esm, Variant::Format_Info); - auto valid = isValidSelectRule(ss.mSelectRule); - if (ss.mValue.getType() != VT_Int && ss.mValue.getType() != VT_Float) - valid = SelectRuleStatus::Invalid; - if (valid == SelectRuleStatus::Invalid) - Log(Debug::Warning) << "Skipping invalid SCVR for INFO " << mId; - else - { - mSelects.push_back(ss); - if (valid == SelectRuleStatus::Ignorable) - Log(Debug::Info) - << "Found malformed SCVR for INFO " << mId << " at index " << ss.mSelectRule[0]; - } + auto filter = DialogueCondition::load(esm, mId); + if (filter) + mSelects.emplace_back(std::move(*filter)); break; } case fourCC("BNAM"): @@ -189,11 +119,8 @@ namespace ESM esm.writeHNOCString("SNAM", mSound); esm.writeHNOString("NAME", mResponse); - for (std::vector::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it) - { - esm.writeHNString("SCVR", it->mSelectRule); - it->mValue.write(esm, Variant::Format_Info); - } + for (const auto& rule : mSelects) + rule.save(esm); esm.writeHNOString("BNAM", mResultScript); diff --git a/components/esm3/loadinfo.hpp b/components/esm3/loadinfo.hpp index c2756e8d9c..a3fb4abffa 100644 --- a/components/esm3/loadinfo.hpp +++ b/components/esm3/loadinfo.hpp @@ -4,8 +4,10 @@ #include #include -#include "components/esm/defs.hpp" -#include "components/esm/refid.hpp" +#include +#include + +#include "dialoguecondition.hpp" #include "variant.hpp" namespace ESM @@ -47,13 +49,6 @@ namespace ESM }; // 12 bytes DATAstruct mData; - // The rules for whether or not we will select this dialog item. - struct SelectStruct - { - std::string mSelectRule; // This has a complicated format - Variant mValue; - }; - // Journal quest indices (introduced with the quest system in Tribunal) enum QuestStatus { @@ -65,7 +60,7 @@ namespace ESM // Rules for when to include this item in the final list of options // visible to the player. - std::vector mSelects; + std::vector mSelects; // Id of this, previous and next INFO items RefId mId, mPrev, mNext; From b34dac1a270b0bc88f9bc063508a3be0e857b463 Mon Sep 17 00:00:00 2001 From: Arnaud Dochain Date: Fri, 12 Apr 2024 15:01:59 +0000 Subject: [PATCH 375/451] Launcher and Wizard french localisation --- files/lang/components_fr.ts | 38 +- files/lang/launcher_fr.ts | 713 ++++++++++++++++++------------------ files/lang/wizard_fr.ts | 308 ++++++++-------- 3 files changed, 532 insertions(+), 527 deletions(-) diff --git a/files/lang/components_fr.ts b/files/lang/components_fr.ts index 309424cda4..706dc1c988 100644 --- a/files/lang/components_fr.ts +++ b/files/lang/components_fr.ts @@ -5,89 +5,91 @@ ContentSelector Select language used by ESM/ESP content files to allow OpenMW to detect their encoding. - + Sélectionnez la langue utilisée dans les fichiers ESM/ESP afin qu'OpenMW détecte leur encodage. ContentSelectorModel::ContentModel Unable to find dependent file: %1 - + Impossible de trouver le fichier de dépendance : %1 Dependent file needs to be active: %1 - + Le fichier de dépendance doit être activé : %1 This file needs to load after %1 - + Ce fichier doit être chargé après %1 ContentSelectorModel::EsmFile <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> - + <b>Auteur :</b> %1<br/><b>Version du format :</b> %2<br/><b>Modifié le :</b> %3<br/><b>Emplacement :</b><br/>%4<br/><br/><b>Description :</b><br/>%5<br/><br/><b>Dépendences : </b>%6<br/> <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> - + <br/><b>Ce fichier de contenu ne peut être désactivé, car il fait partie intégrante d'OpenMW.</b><br/> <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> - + <br/><b>Ce fichier de contenu ne peut être désactivé, car il est activé par un fichier de configuration non contrôlé par l'utilisateur.</b><br/> ContentSelectorView::ContentSelector &Check Selected - + &Activer la sélection &Uncheck Selected - + &Désactiver la sélection &Copy Path(s) to Clipboard - + &Copier l'emplacement dans le presse papier <No game file> - + <Pas de fichier de jeu> Process::ProcessInvoker Error starting executable - + Erreur au démarrage de l'application Error running executable - + Erreur lors de l'exécution de l'application Arguments: - + +Arguments : + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> - + <html><head/><body><p><b>Impossible de trouver %1</b></p><p>L'application est manquante.</p><p>Assurez-vous qu'OpenMW est installé correctement puis réessayez.</p></body></html> <html><head/><body><p><b>Could not start %1</b></p><p>The application is not executable.</p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible de démarrer %1</b></p><p>L'application n'est pas exécutable.</p><p>Assurez-vous d'avoir les bons droits d'accès puis réessayez.</p></body></html> <html><head/><body><p><b>Could not start %1</b></p><p>An error occurred while starting %1.</p><p>Press "Show Details..." for more information.</p></body></html> - + <html><head/><body><p><b>Impossible de démarrer %1</b></p><p>Une erreur est apparue au lancement de %1.</p><p>Cliquez sur "Montrer les détails..." pour plus d'informations.</p></body></html> <html><head/><body><p><b>Executable %1 returned an error</b></p><p>An error occurred while running %1.</p><p>Press "Show Details..." for more information.</p></body></html> - + <html><head/><body><p><b>Le programme %1 a envoyé une erreur.</b></p><p>Une erreur est apparue durant l'exécution de %1.</p><p>Cliquez sur "Montrer les détails..." pour plus d'informations</p></body></html> diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 7f2418c164..f349e196af 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -5,736 +5,736 @@ DataFilesPage Content Files - + Fichiers de données Data Directories - + Dossiers de données Scan directories for likely data directories and append them at the end of the list. - + Parcours les dossiers afin de trouver des <em>dossiers de données</em> et ajoute ceux-ci à la fin de la liste. Append - + Ajouter Scan directories for likely data directories and insert them above the selected position - + Parcours les dossiers afin de trouver des <em>dossiers de données</em> et ajoute ceux-ci au dessus de la position actuellement sélectionnée. Insert Above - + Insérer au dessus Move selected directory one position up - + Déplace le(s) dossier(s) sélectionné(s) d'une position vers le haut. Move Up - + Monter Move selected directory one position down - + Déplace le(s) dossier(s) sélectionné(s) d'une position vers le bas. Move Down - + Descendre Remove selected directory - + Supprime le(s) dossier(s) sélectionné(s) Remove - + Supprimer Archive Files - + Fichiers archive Move selected archive one position up - + Déplace les archives sélectionnées d'une position vers le haut Move selected archive one position down - + Déplace les archives sélectionnées d'une position vers le bas Navigation Mesh Cache - + Mise en cache des mesh de navigation Generate navigation mesh cache for all content. Will be used by the engine to make cell loading faster. - + Génère un cache de mesh de navigation pour tout le contenu du jeu. Celui-ci sera utilisé par le moteur de jeu afin d'accélérer la transition entre les différentes zones de jeu. Update - + Mettre à jour Cancel navigation mesh generation. Already processed data will be saved. - + Annule la génération de mesh de navigation. Les données déjà sauvegardées seront sauvegardées. Cancel - + Annuler MiB - + Mio Content List - + Liste de contenu Select a content list - + Sélectionne une Liste de contenu. New Content List - + Nouvelle Liste de contenu &New Content List - + &Nouvelle Liste de contenu Clone Content List - + Cloner une Liste de contenu Delete Content List - + Supprimer une Liste de contenu Ctrl+N - + Ctrl+N Ctrl+G - + Ctrl+G Ctrl+D - + Ctrl+D Check Selection - + Activer la sélection Uncheck Selection - + Désactiver la sélection Refresh Data Files - + Rafraîchir la liste des fichiers Ctrl+R - + Ctrl+R <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - + <html><head/><body><p>Note : Les fichiers de contenu qui ne font pas partie de la <em>Liste de contenu</em> actuelle sont <span style=" font-style:italic;font-weight: bold">surlignés</span>.</p></body></html> <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - + <html><head/><body><p>Note : Les dossiers de données qui ne font pas partie de la <em>Liste de contenu</em> actuelle sont <span style=" font-style:italic;font-weight: bold">surlignés</span>.</p></body></html> <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> - + <html><head/><body><p>Note : Les archives qui ne font pas partie de la <em>Liste de contenu</em> actuelle sont <span style=" font-style:italic;font-weight: bold">surlignées</span>.</p></body></html> Remove Unused Tiles - + Ignorer les tuiles inutilisées Max Size - + Taille maximale GraphicsPage 0 - + 0 2 - + 2 4 - + 4 8 - + 8 16 - + 16 Custom: - + Personnalisé : Standard: - + Standard : Fullscreen - + Plein écran Windowed Fullscreen - + Fenêtré plein écran Windowed - + Fenêtré Disabled - + Désactivé Enabled - + Activé Adaptive - + Adaptatif FPS - + FPS × - + × Screen - + Écran Resolution - + Résolution Window Mode - + Mode d'affichage Framerate Limit - + Limite de framerate Window Border - + Bordure de fenêtre Anti-Aliasing - + Antialiasing Vertical Synchronization - + Synchronisation verticale ImportPage Form - + Form Morrowind Installation Wizard - + Assistant d'installation de Morrowind Run &Installation Wizard - + Lancer &l'Assistant d'installation Morrowind Settings Importer - + Import des paramètres de Morrowind Browse... - + Parcourir... Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. - + Les polices fournies avec le moteur de jeu original sont floues lorsque l'interface utilisateur est agrandie. De plus, elles ne supportent qu'un faible nombre de caractères. Afin d'éviter ces désagréments, OpenMW propose son propre ensemble de polices. Ces polices sont encodées au format TrueType et ressemblent aux polices originales. Sélectionnez cette option si vous préférez utiliser les polices originales - à la place de celles fournies par OpenMW - ou si vous utilisez vos propres polices au format bitmap. Run &Settings Importer - + Lancer &l'import des paramètres File to Import Settings From: - + Ficher depuis lequel les paramètres seront importés : Import Bitmap Fonts - + Importer les paramètres des polices en bitmap Import Add-on and Plugin Selection - + Importer les extensions et la sélection des modules complémentaires (crée une nouvelle Liste de contenu) Launcher::DataFilesPage English - + Anglais French - + Français German - + Allemand Italian - + Italien Polish - + Polonais Russian - + Russe Spanish - + Espagnol New Content List - + Créer une Liste de contenu Content List name: - + Nom de la Liste de contenu : Clone Content List - + Cloner la Liste de contenu Select Directory - + Sélectionnez un dossier Delete Content List - + Supprimer la Liste de contenu Are you sure you want to delete <b>%1</b>? - + Êtes-vous sûre de vouloir supprimer <b>%1</b> ? Delete - + Supprimer Contains content file(s) - + Contient les fichiers de contenu Will be added to the current profile - + Sera ajouté au profil actuel &Check Selected - + &Active la sélection &Uncheck Selected - + &Désctive la sélection Resolved as %1 - + Localisation : %1 This is the data-local directory and cannot be disabled - + Ceci est le dossier data-local, il ne peut être désactivé. This directory is part of OpenMW and cannot be disabled - + Ce dossier ne peut être désactivé, car il fait partie intégrante d'OpenMW. This directory is enabled in an openmw.cfg other than the user one - + Ce dossier est activé dans un fichier openmw.cfg qui n'est pas celui de l'utilisateur. This archive is enabled in an openmw.cfg other than the user one - + Cette archive est activée dans un fichier openmw.cfg qui n'est pas celui de l'utilisateur. Launcher::GraphicsPage Error receiving number of screens - + Erreur lors de l'obtention du nombre d'écrans <br><b>SDL_GetNumVideoDisplays failed:</b><br><br> - + <br><b>SDL_GetNumVideoDisplays failed:</b><br><br> Screen - + Écran Error receiving resolutions - + Erreur lors de l'obtention des résolutions <br><b>SDL_GetNumDisplayModes failed:</b><br><br> - + <br><b>SDL_GetNumDisplayModes failed:</b><br><br> <br><b>SDL_GetDisplayMode failed:</b><br><br> - + <br><b>SDL_GetDisplayMode failed:</b><br><br> Launcher::ImportPage Error writing OpenMW configuration file - + Erreur lors de l'écriture du fichier de configuration Morrowind configuration file (*.ini) - + Fichier de configuration de Morrowind (*.ini) Importer finished - + Importation terminée Failed to import settings from INI file. - + Échec lors de l'importation du fichier INI. <html><head/><body><p><b>Could not open or create %1 for writing </b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible d'ouvrir ou de créer %1 en écriture </b></p><p>Veuillez vous assurer que vous avez les bons droits d'accès puis réessayez.</p></body></html> Launcher::MainDialog Close - + Fermer Launch OpenMW - + Lancer OpenMW Help - + Aide Error opening OpenMW configuration file - + Erreur lors de l'ouverture du fichier de configuration d'OpenMW First run - + Premier lancement Run &Installation Wizard - + Lancer l'Assistant d'installation Skip - + Passer OpenMW %1 release - + OpenMW %1 release OpenMW development (%1) - + OpenMW development (%1) Compiled on %1 %2 - + Compilé le %1 %2 Error detecting Morrowind installation - + Erreur lors de la détection de l'installation de Morrowind Run &Installation Wizard... - + Lancer l'Assistant d'installation Error reading OpenMW configuration files - + Erreur lors de la lecture du fichier de configuration d'OpenMW Error writing OpenMW configuration file - + Erreur lors de l'écriture dans le fichier de configuration d'OpenMW Error writing user settings file - + Erreur lors de l'écriture du fichier de paramètres utilisateur Error writing Launcher configuration file - + Erreur lors de l'écriture dans le fichier de paramètres du Lanceur No game file selected - + Aucun fichier de jeu sélectionné <html><head/><body><p><b>Welcome to OpenMW!</b></p><p>It is recommended to run the Installation Wizard.</p><p>The Wizard will let you select an existing Morrowind installation, or install Morrowind for OpenMW to use.</p></body></html> - + <html><head/><body><p><b>Bienvenue sur OpenMW !</b></p><p>Il est recommandé de lancer l'Assistant d'installation .</p><p>L'assistant vous permettra de sélectionner une installation de Morrowind existante, ou d'installer Morrowind pour son utilisation dans OpenMW.</p></body></html> <br><b>Could not open %0 for reading:</b><br><br>%1<br><br>Please make sure you have the right permissions and try again.<br> - + <br><b>Impossible d'ouvrir %0 en lecture :</b><br><br>%1<br><br>Assurez vous d'avoir les bons droits d'accès puis réessayez.<br> <br><b>Could not open %0 for reading</b><br><br>Please make sure you have the right permissions and try again.<br> - + <br><b>Impossible d'ouvrir %0 en lecture :</b><br><br>%1<br><br>Assurez vous d'avoir les bons droits d'accès puis réessayez.<br> <br><b>Could not find the Data Files location</b><br><br>The directory containing the data files was not found. - + <br><b>Impossible de trouver l'emmplacement de Data Files</b><br><br>Le dossier contenant les fichiers de données n'a pas été trouvé. <br>The problem may be due to an incomplete installation of OpenMW.<br>Reinstalling OpenMW may resolve the problem.<br> - + <br>Ce problème peut survenir lors d'une installation incomplète d'OpenMW.<br>Réinstaller OpenMW pourrait résoudre le problème.<br> <br><b>Could not open or create %0 for writing</b><br><br>Please make sure you have the right permissions and try again.<br> - + <br><b>Impossible d'ouvrir ou de créer %0 en écriture :</b><br><br>%1<br><br>Assurez vous d'avoir les bons droits d'accès puis réessayez.<br> <br><b>You do not have a game file selected.</b><br><br>OpenMW will not start without a game file selected.<br> - + <br><b>Vous n'avez pas sélectionné de fichier de jeu.</b><br><br>OpenMW ne peut démarrer sans qu'un fichier de jeu soit sélectionné.<br> Error creating OpenMW configuration directory: code %0 - + Erreur lors de la création du dossier de configuration d'OpenMW : code %0 <br><b>Could not create directory %0</b><br><br>%1<br> - + <br><b>Impossible de créer le dossier %0</b><br><br>%1<br> Launcher::SettingsPage Text file (*.txt) - + Fichier texte (*.txt) MainWindow OpenMW Launcher - + Lanceur d'OpenMW OpenMW version - + Version d'OpenMW toolBar - + toolBar Data Files - + Données de jeu Allows to setup data files and directories - + Configurez et sélectionnez les fichiers, dossiers et archives de jeu Settings - + Options Allows to tweak engine settings - + Modifiez les options du moteur de jeu Import - + Importer Allows to import data from original engine - + Importez des données depuis le moteur de jeu original Display - + Affichage Allows to change display settings - + Modifiez les paramètres d'affichage QObject Select configuration file - + Sélectionnez un fichier de configuration Select script file - + Sélectionnez un fichier de script SelectSubdirs Select directories you wish to add - + Sélectionnez le dossier que vous désirez ajouter SettingsPage <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> - + <html><head/><body><p>Rends le changement de disposition des marchands due au Marchandage permanent.</p></body></html> <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> - + <html><head/><body><p>Permets aux compagnons et aux escortes de démarrer le combat avec des ennemis entrés en combat avec eux ou avec le joueur. Si l'option est désactivée, ils attendent que les ennemis les attaquent en premier.</p></body></html> <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> - + <html><head/><body><p>Cette option retire le plafond sur l'effet magique Atténuation de Fatigue, comme c'est le cas avec Absorption de Fatigue. Cette option vous permet d'assommer des personnages en utilisant cet effet magique.</p></body></html> Uncapped Damage Fatigue - + Domages de fatique déplafonnés <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> - + <html><head/><body><p>Si cette option est activée, le jeu interrompt le combat avec les PNJ affectés par l'effet magique Apaisement à chaque frame. Ce comportement similaire à celui du moteur d'origine, sans MCP.</p></body></html> <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> - + <html><head/><body><p>Si l'option est cochée, la valeur des gemmes sprirituelles dépend uniquement de la puissance de l'âme contenue.</p></body></html> <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> - + <html><head/><body><p>Cette option fait que le joueur nage légèrement au-dessus de la ligne de visée. S'applique uniquement dans le mode Vue à la troisième personne. Ceci facilite la nage, hors plongée.</p></body></html> <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> - + <html><head/><body><p>Cette option rend possible le vol d'objets à un PNJ tombé à terre durant un combat.</p></body></html> <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> - + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>Lorsque cette option est activée, un mesh de navigation est construit en arrière-plan à partir de la géométrie de l'environnement. Celui-ci est alors utilisé pour la recherche d'itinéraire.</p><p>Lorsqu'elle est désactivée, seule la grille des chemins est utilisée pour tracer les itinéraires.</p><p>Cette option peut affecter de façon significative les systèmes à simple cœur, lorsque le joueur passe d'un environnement intérieur à l'environnement extérieur. Ce paramètre peut aussi affecter, dans une moindre mesure, les systèmes multicœurs. La lenteur due à la génération de mesh de navigation des systèmes multicœurs peut dépendre des autres options ainsi que de la puissance du système. Se déplacer au sein de l'environnement extérieur, entrer et sortir d'un lieu génère un mesh de navigation. Les PNJ et les créatures peuvent ne pas trouver les mesh de navigation générés autour d'eux. Désactivez cette option si vous désirez avoir une intelligence artificielle à l'ancienne où les ennemis ne savent pas quoi faire lorsque le joueur se tient derrière un rocher et lance des boules de feu.</p></body></html> <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> - + <html><head/><body><p>Si l'option est activée, les PNJ tentent des manœuvres d'évitement afin d'éviter d'entrer en collision les uns avec les autres.</p></body></html> <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> - + <html><head/><body><p>N'utilise pas le poids de la race du PNJ pour calculer sa vitesse de mouvement.</p></body></html> <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> - + <html><head/><body><p>Lorsque cette option est activée, le joueur est autorisé à piller créatures et PNJ (p. ex. les créatures invoquées) durant leur animation de mort, si elles ne sont pas en combat. Dans ce cas, le jeu incrémente le conteur de mort et lance son script instantanément.</p><p>Lorsque cette option est désactivée, le joueur doit attendre la fin de l'animation de mort. Dans ce cas, l'utilisation de l'exploit des créatures invoquées (piller des créatures invoquées telles que des Drémoras ou des Saintes Dorées afin d'obtenir des armes de grandes valeurs) est rendu beaucoup plus ardu. Cette option entre en confit avec les Mods de mannequin. En effet, ceux-ci utilisent SkipAnim afin d'éviter la fin de l'animation de mort.</p></body></html> <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> - + <html><head/><body><p>L'option donne aux créatures non armées la capacité d'endommager les pièces d'armure, comme le font les PNJ et les créatures armées.</p></body></html> Off - + Inactif <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> - + <html><head/><body><p>Détermine le nombre de thread qui seront générés en arrière-plan pour mettre à jour la physique du jeu. La valeur 0 signifie que ces mises à jour seront calculées par le thread principal.</p><p>Une valeur supérieure à 1 requière que la librairie Bullet soit compilée avec le support multithread.</p></body></html> Cylinder - + Cylindrique Visuals - + Visuels Animations - + Animations <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - + <html><head/><body><p>Anime l'utilisation d'objet magique, de façon similaire à l'utilisation des sorts.</p></body></html> <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> - + <html><head/><body><p>Cette option rend les mouvements des PNJ et du joueur plus souple. Recommandé si l'option "Se tourner en direction du mouvement" est activée.</p></body></html> <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> - + <html><head/><body><p>Charge les fichiers d'animation groupée KF et les fichiers de structure de squelettes depuis le dossier Animation.</p></body></html> <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> - + <html><head/><body><p>Affecte les mouvements latéraux et diagonaux. Activer cette option rend les mouvements plus réalistes.</p><p>Si elle est désactivée, le corps entier du personnage joueur est pointé dans la direction de visée. Les mouvements diagonaux n'ont pas d'animation spécifique et glissent sur le sol.</p><p>Si elle est activée, le bas du corps du personnage joueur est orienté suivant la direction du mouvement, le haut du corps est orienté partiellement, et la tête est alignée avec la direction de visée. En mode combat, cet effet n'est appliqué qu'aux mouvements diagonaux. Hors mode combat, cet effet est aussi appliqué aux mouvements purement latéraux. Cette option affecte aussi la nage, le moteur de jeu oriente alors tout le corps dans la direction du mouvement.</p></body></html> <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> - + <html><head/><body><p>Cette option permet d'afficher les armes rengainées dans leur étui (p. ex. carquois et fourreau). Elle nécessite des modèles 3D fournis pas un mod.</p></body></html> <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> - + <html><head/><body><p>Cette option permet d'afficher les boucliers rengainés. Elle nécessite des modèles 3D fournis pas un mod.</p></body></html> <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> - + <html><head/><body><p>En vue à la troisième personne, la caméra se balance avec mouvement d'animation du joueur. Activer cette option désactive ce balancement de sorte que le personnage joueur se déplace indépendamment de son animation. Ce comportement était activé par défaut dans les versions d'OpenMW 0.48 et antérieures.</p></body></html> Shaders - + Shaders <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> - + <html><body><p>Lorsque cette option est activée, les normal maps (textures d'orientation de la surface) sont automatiquement reconnues et utilisées, lorsque le fichier est nommé correctement.</p><p>Par exemple, si une texture de base est nommée foo.dds, la texture de normal maps devrait être nommée foo_n.dds (voir 'normal map pattern' dans la documentation pour plus d'informations).</p><p>Si l'option est désactivée, les normal maps sont utilisées uniquement si elles sont listées explicitement dans le fichier de mesh (fichiers .nif ou .osg). Cette option affecte les objets.</p></body></html> <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> - + <html><head/><body><p>Voir "Utilisation auto des normal maps pour les objets". Affecte le terrain.</p></body></html> <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately @@ -742,734 +742,737 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov the specular map texture would have to be named foo_spec.dds). If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.osg file, not supported in .nif files). Affects objects.</p></body></html> - + <html><body><p>Si cette option est activée, les specular maps (textures indiquant si un objet est brillant ou mat) sont automatiquement reconnues et utilisées, lorsque le fichier est nommé correctement.</p><p>Par exemple, si une texture de base est nommée foo.dds, la texture de specular maps devrait être nommée foo_spec.dds (voir 'specular map pattern' dans la documentation pour plus d'informations, en anglais).</p><p>Si le paramètre est désactivé, les specular maps sont utilisées uniquement si elles sont listées explicitement dans le fichier de mesh (fichiers .osg, non supporté par le format nif). Cette option affecte les objets.</p></body></html> <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> - + <html><head/><body><p>Si l'option est activée et qu'un fichier dont le nom respecte la structure des specular maps (voir Utilisation auto des specular maps pour les objets), le moteur de jeu utilise cette texture comme "diffuse specular map" : Cette texture doit contenir une couche (habituelle) de couleur RVB et un facteur de spécularité (≃brillance) dans le canal alpha.</p></body></html> <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. Affected objects will use shaders. </p></body></html> - + <html><head/><body><p>En temps normal, les réflexions dues au placage d'environnement (environment map) ne sont pas affectées par l'éclairage de la scène. En conséquence, les objets affectés par le placage d'environnement (et de relief) ont tendance à briller dans le noir. Le "Morrowind Code Patch" a, pour remédier à ça, inclus une option qui applique le placage d'environnement (et de relief) avant d'appliquer l'éclairage. Cette option est son équivalent. Les objets affectés utilisent toujours les shaders.</p></body></html> <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> - + <html><body><p>Cette option permet à l'antialiasing à multiéchantillonnage (MSAA) de fonctionner avec le test alpha des mesh. Ceci produit un meilleur rendu des bords, sans pixélisation. Cette option peut affecter négativement les performances.</p></body></html> <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> - + <html><body><p>Cette option active la génération douce des effets de particules. Cette technique adoucit l'intersection entre chaque particule et les éléments de géométrie opaque à l'aide d'un fondu progressif entre les deux.</p></body></html> <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> - + <html><body><p>Lorsque cette option est activée, le moteur réalise une simulation de couverture préservée des mipmaps (coverage-preserving mipmaps). Ceci empêche les mesh testés en transparence (alpha test) de diminuer en taille lorsqu'ils s'éloignent. En contrepartie, cette option tend à agrandir les mesh ayant déjà une couverture préservée des mipmaps. Référez-vous à l'installation du contenu additionnel (mod) pour savoir quelle option est la mieux adaptée à celui-ci.</p></body></html> <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> - + <html><body><p>Expérimental ! Cette option empêche la pluie et la neige de tomber au travers des promontoires et des toits.</p></body></html> Fog - + Brouillard <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> - + <html><body><p>Si cette option est désactivée, le brouillard s'épaissit linéairement avec la distance jusqu'à atteindre le plan de coupure perpendiculaire à la caméra (clipping plane) à la distance de coupure. Ceci cause des distorsions sur les bords de l'écran.</p><p>Si elle est activée, le brouillard utilise la distance de chaque objet avec la caméra (appelée distance euclidienne) pour calculer la densité de brouillard. Cette option rend le brouillard moins artificiel, particulièrement pour les larges champs de vision.</p></body></html> <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> - + <html><body><p>Si l'option est activée, le moteur de jeu utilise une décroissance exponentielle de la visibilité due au brouillard.</p><p>Si elle est désactivée, il utilise une décroissance linéaire de la visibilité due au brouillard.</p></body></html> <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> - + <html><head/><body><p>Si l'option est activée, le moteur de jeu réduit la visibilité du plan de coupure du brouillard en effectuant un fondu avec le ciel.</p></body></html> <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> - + <html><head/><body><p>Fraction de la distance d'affichage maximale à partir de laquelle le fondu avec le ciel commence.</p></body></html> Terrain - + Terrain <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> - + <html><body><p>Cette option contrôle la taille apparente minimale que les objets doivent avoir pour être visibles à l'écran. Le rapport entre la taille de l'objet et sa distance à la caméra est comparé à cette valeur seuil. Plus cette valeur est faible, plus le nombre d'objets visibles à l'écran sera élevé.</p></body></html> <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> - + <html><body><p>Si l'option est activée, le moteur utilise la pagination et différents niveau de détails (LOD) afin d'afficher le terrain entier.</p><p>Si elle est désactivée, seul le terrain de jeu actif est affiché.</p></body></html> <html><head/><body><p>Use object paging for active cells grid.</p></body></html> - + <html><body><p>Si l'option est activée, utilise la pagination des objets pour rendre l'espace de jeu actif.</p></body></html> <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> - + <html><body><p>Si cette option est activée, le moteur prend en charge les "day night switch nodes" des modèles 3D. Ceci permet de sélectionner entre jour et nuit l'apparence de ces modèles.</p></body></html> Post Processing - + Post-processing <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> - + <html><body><p>Si cette option est activée, le post-traitement est lui aussi activé (les shaders de post-traitement sont configurables en jeu).</p></body></html> <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> - + <html><head/><body><p>Si l'option est activée, le moteur réalise une seconde passe de rendu des objets transparents, en forçant leur découpe par le canal alpha de leurs textures.</p></body></html> <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> - + <html><head/><body><p>Cette option contrôle la vitesse selon laquelle l'adaptation visuelle change d'une image à la suivante. Une valeur faible correspond à une adaptation lente.</p></body></html> Audio - + Audio Select your preferred audio device. - + Sélectionnez votre périphérique audio principal. Default - + Par défaut This setting controls HRTF, which simulates 3D sound on stereo systems. - + Cette option contrôle l'HRTF, qui émule la spatialisation 3D du son pour un système stéréo. HRTF - + HRTF Automatic - + Automatique On - + Actif Select your preferred HRTF profile. - + Sélectionnez le profil HRTF qui vous convient le mieux. Interface - + Interface <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> - + <html><body><p>Cette option agrandit ou diminue l'interface utilisateur. Une valeur de 1 correspond à sa taille nominale.</p></body></html> <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> - + <html><body><p>Cette option permet d'activer la durée de vie restante des effets magiques et des lampes. Cette durée restante s'affiche en passant la souris au-dessus de l'icône de l'effet magique.</p></body></html> <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> - + <html><body><p>Lorsque cette option est activée, les sujets de conversation de la fenêtre de dialogue auront des couleurs différentes si ceux-ci sont spécifiques au PNJ ou qu'ils ont déjà été lus autre part. Ces couleurs peuvent être changées dans le fichier settings.cfg.</p></body></html> Size of characters in game texts. - + <html><body><p>Taille des caractères des textes du jeu.</p></body></html> <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> - + <html><body><p>Cette option permet de zoomer en jeu sur la carte (carte locale et carte du monde).</p></body></html> <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> - + <html><body><p>Si cette option est activée, les contenants compatibles avec "Graphic Herbalism" l'utilisent, au lieu d'ouvrir le menu de son contenu.</p></body></html> <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - + <html><body><p>Lorsque cette option est activée, les bonus de dommages des flèches et des carreaux apparaissent dans l'infobulle de l'objet.</p></body></html> <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> - + <html><body><p>Lorsque cette option est activée, la portée et la rapidité d'attaque des armes de mêlées apparaissent dans l'infobulle de l'objet.</p></body></html> <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> - + <html><body><p>Étends les menus, les écrans de chargement et autres éléments d'interface aux formats d'image du jeu.</p></body></html> <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> - + <html><body><p>Lorsque l'option est activée, affiche les chances de succès d'un enchantement dans le menu des enchantements.</body></html> <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> - + <html><body><p>Empêche les marchands d'équiper les objets qui leur sont vendus.</p></body></html> <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> - + <html><body><p>Lorsque l'option est activée, les entraîneurs choisissent les compétences à entraîner en utilisant uniquement les points de compétences de base. Ceci permet d'améliorer les compétences de marchandages de l'entraîneur, sans que le marchandage devienne une compétence proposée.</p></body></html> Miscellaneous - + Divers Saves - + Sauvegardes <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> - + <html><body><p>Cette option affiche le temps de jeu de chaque sauvegarde dans leur menu de sélection.</p></body></html> JPG - + JPG PNG - + PNG TGA - + TGA Testing - + Testeurs These settings are intended for testing mods and will cause issues if used for normal gameplay. - + Ces options sont destinées aux testeurs de contenus additionnels (mods). Ils poseront problème lors d'une partie normale. <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> - + <html><body><p>Lorsque cette option est activée, OpenMW capture le curseur de la souris.</p><p>Lorsque le jeu est en mode "Regard/Déplacement", OpenMW centre le curseur au milieu de l'écran; sans tenir compte de cette option (le curseur/viseur est toujours au centre de la fenêtre d'OpenMW). Néanmoins, lorsqu'un menu est ouvert, cette option permet de récupérer le contrôle de la souris. En effet, lorsque l'option est active, le mouvement du curseur s'arrête aux bords de la fenêtre et empêche d'accéder à d'autres applications. Tandis que, lorsque l'option est désactivée, le curseur peut sortir librement de la fenêtre et accéder au bureau.</p><p>Cette option ne s'applique pas à l'écran lorsque la touche Échap a été enfoncée, dans ce cas, le curseur n'est jamais capturé. Cette option n'agit pas non plus sur "Atl-Tab" ou toute autre séquence de touches utilisée par le système d'exploitation pour récupérer le contrôle du curseur de la souris. </p><p>Cette option interagit avec l'option de minimisation de la fenêtre lors de la perte de focus en modifiant ce qui est considéré comme une perte de focus. Par exemple, avec une configuration à doubles écrans, il peut être plus agréable d'accéder au second écran avec cette option désactivée.</p><p>Note aux développeurs : Il est préférable de désactiver cette option lorsque le jeu tourne dans un débogueur, ceci afin d'empêcher le curseur de la souris de devenir inutilisable lorsque le jeu s'arrête sur un breakpoint.</p></body></html> Browse… - + Parcourir... Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. - + Les volumes de collision sont utilisés à la fois par la simulation de la physique du jeu et par la génération de mesh de navigation utilisé pour les choix d'itinéraires. Les collisions cylindriques donnent la meilleure correspondance entre l'itinéraire choisi et la capacité à les emprunter. Changer la valeur de cette option affecte la génération de mesh de navigation, dès lors les mesh de navigation précédemment mis en cache ne seront plus compatibles. Gameplay - + Jouabilité <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> - + <html><body><p>Si l'option est activée, une munition magique est requise pour contourner la résistance ou la faiblesse normale d'une arme.</p><p>Si l'option est désactivée, une arme à distance magique ou une munition magique est requise.</p></body></html> <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> - + <html><body><p>Lorsque cette option est activée, elle permet aussi aux armes enchantées, non indiquées comme étant magique, de contourner la résistance de l'arme, comme c'est le cas dans le moteur original.</p></body></html> cells - + cellules Shadows - + Ombres <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> - + <html><body><p>Type de méthode utilisée pour calculer de "compute scene bound" (limite de scène). Le choix "bordures" (par défaut) donne un bon équilibre entre qualité des ombres et performances, "primitives" pour un meilleur rendu ou "aucun" pour éviter ce calcul.</p></body></html> <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> - + <html><body><p>64 unités de jeu correspondent à 1 yard, c'est-à-dire environ 0.9 m.</p></body></html> unit(s) - + unité(s) <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> - + <html><head/><body><p>Cette option affiche l'ombre des PNJ et des créatures. Elle peut avoir un impact faible sur les performances.</p></body></html> 512 - + 512 1024 - + 1024 2048 - + 2048 4096 - + 4096 <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> - + <html><body><p>Fraction de la limite ci-dessus à partir de laquelle les ombres commencent à disparaître.</p></body></html> <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> - + <html><body><p>Cette option affiche l'ombre du personnage joueur. Elle pourrait avoir un impact très faible sur les performances.</p></body></html> <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> - + <html><body><p>La résolution de chaque carte d'ombre. Augmenter cette valeur, de façon importante, augmente la qualité des ombres, mais peut impliquer un impact mineur sur les performances.</p></body></html> <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> - + <html><body><p>Distance à la caméra à partir de laquelle les ombres disparaissent entièrement.</p></body></html> <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> - + <html><head/><body><p>Cette option affiche l'ombre des objets. Elle peut avoir un impact significative sur les performances.</p></body></html> <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> - + <html><body><p>À cause de certaines limitations avec les données de Morrowind, seuls les personnages et les créatures peuvent projeter une ombre en intérieur, ceci peut déconcerter certains joueurs.</p><p>Cette option n'a aucun effet si les ombres des PNJ/créature et du personnage joueur n'ont pas été activées.</p></body></html> <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> - + <html><head/><body><p>Cette option affiche l'ombre des terrains. Elle peut avoir un impact faible sur les performances et la qualité des ombres.</p></body></html> Lighting - + Éclairage Tooltip - + Infobulles Crosshair - + Réticule de visée Screenshots - + Captures d'écran <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> - + <html><body><p>Cette option donne aux PNJ/créatures la capacité de nager en surface lorsqu'ils suivent un autre acteur, indépendamment de leur capacité à nager. Elle fonctionne uniquement lorsque l'utilisation des mesh de navigation pour le choix d'itinéraire est activée.</p></body></html> <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> - + <html><body><p>L'effet réfléchi des sorts d'absorption n'est pas appliqué, comme dans le moteur original.</p></body></html> <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> - + <html><body><p>Distance maximale d'affichage des sources lumineuses (en unité de distance).</p><p>Mettez cette valeur à 0 pour une distance d'affichage infinie.</p></body></html> <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> - + <html><body><p>Nombre maximum de sources lumineuses par objet.</p><p>Une valeur faible mène à des apparitions tardives des sources lumineuses similaires à celles obtenues avec la méthode d'éclairage traditionnelle.</p></body></html> <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> - + <html><body><p>Fraction de la distance maximale d'une source à partir de laquelle l'intensité lumineuse commence à décroître.</p><p>Sélectionnez une valeur basse pour une transition douce ou une valeur plus élevée pour une transition plus abrupte.</p></body></html> <html><head/><body><p>Set the internal handling of light sources.</p> <p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> <p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> <p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> - + <html><body><p>Définit la gestion des sources lumineuses :</p> +<p>"Traditionnelle" Chaque objet est éclairé par 8 sources lumineuses. Cet méthode est la plus proche du jeu original.</p> +<p>"Shaders (mode de compatibilité)" supprime la limite des 8 sources lumineuses. Cette méthode permet d'éclairer la végétation au sol, mais aussi de configurer à quel distance une source lumineuse s'estompe. Ce choix est recommandé pour les ordinateurs plus anciens avec un nombre de sources lumineuses proche de 8.</p> +<p>"Shaders" offre tous les bénéfices apportés par "Shaders (mode de compatibilité)", mais utilise une approche moderne. Celle-ci permet, sur du matériel moderne, d'augmenter le nombre de sources lumineuses par objet sans perte de performance."</p></body></html> Legacy - + Traditionnelle Shaders (compatibility) - + Shaders (mode de compatibilité) <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> - + <html><body><p>Multiplicateur pour le rayon de la sphère incluant les sources lumineuses.</p><p>Un multiplicateur plus élevé permet une extinction plus douce, mais applique un plus grand nombre de sources lumineuses sur chaque objet.</p><p>Ce paramètre ne modifie ni l'intensité ni la luminance des lumières.</body></html> <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> - + <html><head/><body><p>Luminosité ambiante minimale en intérieur.</p><p>Augmentez cette valeur si les intérieurs vous semblent trop sombres.</p></body></html> In third-person view, use the camera as the sound listener instead of the player character. - + En vue à la troisième personne, le jeu reproduit le son reçu par la caméra au lieu de celui reçu par tête du personnage joueur. Permanent Barter Disposition Changes - + Disposition au Marchandage permanente Classic Calm Spells Behavior - + Comportement d'origine pour le sort d'Apaisement NPCs Avoid Collisions - + Les PNJ évitent les collisions Soulgem Values Rebalance - + Rééquilibrage de la valeur des gemmes sprirituelles Day Night Switch Nodes - + Support des noeuds jour/nuit Followers Defend Immediately - + Les compagnons se défendent immédiatement Only Magical Ammo Bypass Resistance - + Seules les munitions contournent la résistance Graphic Herbalism - + Support de Graphic herbalism Swim Upward Correction - + Correction verticale de la nage Enchanted Weapons Are Magical - + Rendre magiques les armes enchantées Merchant Equipping Fix - + Correctif : équipement des marchands Can Loot During Death Animation - + Pillage possible durant l'animation de mort Classic Reflected Absorb Spells Behavior - + Comportement traditionnel de la réflexion des sorts d'absorbtion Unarmed Creature Attacks Damage Armor - + L'attaque des créatures non armées endomage les armures Affect Werewolves - + S'applique aux loups garoux Do Not Affect Werewolves - + Ne s'applique pas aux loups garoux Background Physics Threads - + Thread(s) d'arrière plan dédié(s) à la physique Actor Collision Shape Type - + Volume de collison pour les personnages Axis-Aligned Bounding Box - + Volume englobant aligné à l'axe Rotating Box - + Boite tournante Smooth Movement - + Mouvements lissés Use Additional Animation Sources - + Utiliser des sources d'animations additionnelles Weapon Sheathing - + Arme rengainée Shield Sheathing - + Bouclier rengainé Player Movement Ignores Animation - + Le mouvement du personnage joueur ignore son animation Use Magic Item Animation - + Anime l'utilisation d'objets magique Auto Use Object Normal Maps - + Utilisation auto des normal maps pour les objets Soft Particles - + Apparition douce des particules Auto Use Object Specular Maps - + Utilisation auto des specular maps pour les objets Auto Use Terrain Normal Maps - + Utilisation auto des normal maps pour le terrain Auto Use Terrain Specular Maps - + Utilisation auto des specular maps pour le terrain Use Anti-Aliased Alpha Testing - + Utiliser l'alpha testing pour l'anti-aliasing Bump/Reflect Map Local Lighting - + Plaquage de rugosité/environnement sous lumière locale Weather Particle Occlusion - + Occlusion des particules liées à la météo Exponential Fog - + Brouillard exponentiel Radial Fog - + Brouillard radial Sky Blending Start - + Début du fondu du ciel Sky Blending - + Fondu du ciel Object Paging Min Size - + Taille min des objets pour leur pagination Viewing Distance - + Distance d'affichage Distant Land - + Terrain distant Active Grid Object Paging - + Pagination pour l'espace actif Transparent Postpass - + Passe de transparence différée Auto Exposure Speed - + Vitesse d'auto-exposition Enable Post Processing - + Activer le post-traitement Shadow Planes Computation Method - + Méthode de calcul pour les plans d'ombre Enable Actor Shadows - + Ombre des PNJ/créatures Fade Start Multiplier - + Début de l'atténuation (multiplicateur) Enable Player Shadows - + Ombre du personnage joueur Shadow Map Resolution - + Résolution des crates d'ombres Shadow Distance Limit: - + Limite de distance des ombres Enable Object Shadows - + Ombre des objets Enable Indoor Shadows - + Ombres en intérieur Enable Terrain Shadows - + Ombre des terrains Maximum Light Distance - + Portée maximale des sources lumineuse Max Lights - + Nombre maximum de sources lumineuses Lighting Method - + Méthode d'illumination Bounding Sphere Multiplier - + Multiplicateur de portée des sphères lumineuses Minimum Interior Brightness - + Luminosité intérieure minimale Audio Device - + Périphérique audio HRTF Profile - + Profil HRTF Tooltip and Crosshair - + Infobulles et réticule de visée GUI Scaling Factor - + Échelle de l'interface Show Effect Duration - + Afficher la durée des effets Change Dialogue Topic Color - + Changer la couleur des sujets de conversation Font Size - + Taille des polices Show Projectile Damage - + Afficher les dommages des projectiles Show Melee Info - + Afficher les infos de mêlée Stretch Menu Background - + Étendre les arrière-plans Show Owned Objects - + Afficher la possession des objets Show Enchant Chance - + Afficher les chances d'enchantement Maximum Quicksaves - + Nombre maximum de sauvegardes rapides Screenshot Format - + Format des captures Grab Cursor - + Capturer le curseur Default Cell - + la cellule par défaut Bounds - + bordures Primitives - + primitives None - + aucune Always Allow Actors to Follow over Water - + Toujours permettre aux PNJ/créatures à vous suivre sur l'eau Racial Variation in Speed Fix - + Correction de la variation raciale pour la vitesse Use Navigation Mesh for Pathfinding - + Utiliser les mesh de navigation pour la recherche d'itinéraires Trainers Choose Offered Skills by Base Value - + Entraîneurs : choix de compétences à partir des valeurs de base Steal from Knocked out Actors in Combat - + Vol possible aux PNJ/créatures évanouies en combat Factor Strength into Hand-to-Hand Combat - + Multiplicateur de force pour le combat à mains nues Turn to Movement Direction - + Se tourner en direction du mouvement Adjust Coverage for Alpha Test - + Ajuster la couverture pour l'alpha test Use the Camera as the Sound Listener - + Utiliser le son arrivant à la caméra Can Zoom on Maps - + Permettre le zoom sur la carte Add "Time Played" to Saves - + Ajoute le temps de jeu aux sauvegardes Notify on Saved Screenshot - + Notifier l'enregistrement des captures d'écran Skip Menu and Generate Default Character - + Passer le menu principal et générer un personnage standard Start Default Character at - + Placer le personnage par défaut dans Run Script After Startup: - + Script à lancer après démarrage : diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index ab0b01af73..6c655885d2 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -5,672 +5,672 @@ ComponentSelectionPage WizardPage - + Assistant d'installation d'OpenMW Select Components - + Sélection des composantes de jeu Which components should be installed? - + Quelles composantes du jeu doivent être installées ? <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> - + <html><head/><body><p>Sélectionnez quelle(s) extension(s) officielle(s) doivent être installée(s). Pour une meilleure expérience de jeu, il est recommandé d'installer toutes les extensions.</p><p><span style=" font-weight:bold;">Note :</span> Il est possible d'installer les extensions plus tard en relançant l'Assistant d'installation.<br/></p></body></html> Selected components: - + Composantes sélectionnées : ConclusionPage WizardPage - + Assistant d'installation d'OpenMW Completing the OpenMW Wizard - + Fin de l'Assistant d'installation d'OpenMW Placeholder - + Placeholder ExistingInstallationPage WizardPage - + Assistant d'installation d'OpenMW Select Existing Installation - + Sélection d'une installation existante Select an existing installation for OpenMW to use or modify. - + Sélectionnez une installation existante pour l'utiliser ou la modifier avec OpenMW Detected installations: - + Installation(s) détectée(s) : Browse... - + Parcourir... ImportPage WizardPage - + Assistant d'installation d'OpenMW Import Settings - + Import des paramètres Import settings from the Morrowind installation. - + Importe les paramètres depuis une installation de Morrowind. <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> - + <html><head/><body><p>Afin de fonctionner correctement, OpenMW nécessite d'importer les paramètres depuis le fichier de configuration de Morrowind.</p><p><span style=" font-weight:bold;">Note :</span> Il est possible d'importer les paramètres plus tard en relançant l'Assistant d'installation.</p><p/></body></html> Import Settings From Morrowind.ini - + Importer les paramètres depuis le fichier Morrowind.ini Import Add-on and Plugin Selection - + Importer les extensions et la sélection de modules complémentaires Import Bitmap Fonts Setup From Morrowind.ini - + Importer les paramètres des polices en bitmap depuis Morrowind.ini Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. - + Les polices fournies avec le moteur de jeu original sont floues lorsque l'interface utilisateur est agrandie. De plus, elles ne supportent qu'un faible nombre de caractères. Afin d'éviter ces désagréments, OpenMW propose son propre ensemble de polices. Ces polices sont encodées au format TrueType et sont fort similaires aux polices originales. Sélectionnez cette option si vous préférez utiliser les polices originales - à la place de celles fournies par OpenMW - ou si vous utilisez vos propres polices au format bitmap. InstallationPage WizardPage - + Assistant d'installation d'OpenMW Installing - + Installation en cours Please wait while Morrowind is installed on your computer. - + Veuillez patienter pendant l'installation de Morrowind sur votre ordinateur. InstallationTargetPage WizardPage - + Assistant d'installation d'OpenMW Select Installation Destination - + Sélection de l'emplacement de l'installation Where should Morrowind be installed? - + À quel emplacement Morrowind doit-il être installé ? <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> Morrowind will be installed to the following location. - + Morrowind sera installé à l'emplacement suivant : Browse... - + Parcourir... IntroPage WizardPage - + Assistant d'installation d'OpenMW Welcome to the OpenMW Wizard - + Bienvenue sur l'assistant d'installation d'OpenMW This Wizard will help you install Morrowind and its add-ons for OpenMW to use. - + Cet assistant vous aidera à installer Morrowind et ses extensions afin que vous lanciez le jeu avec OpenMW. LanguageSelectionPage WizardPage - + Assistant d'installation d'OpenMW Select Morrowind Language - + Sélection de la langue de Morrowind What is the language of the Morrowind installation? - + Dans quelle langue est cette installation de Morrowind ? <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - + <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> Select the language of the Morrowind installation. - + Sélectionnez la langue de cette installation de Morrowind. MethodSelectionPage WizardPage - + Assistant d'installation d'OpenMW Select Installation Method - + Méthode d'installation <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> - + <html><head/><body><p>Sélectionnez une méthode d'installation de <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> Retail CD/DVD - + Copie CD/DVD physique <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - + <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> Install from a retail disc to a new location. - + Installer depuis un CD/DVD et choisir la destination sur l'ordinateur. Existing Installation - + Installation existante <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> Select an existing installation. - + Sélectionnez une installation existante. Don't have a copy? - + Vous n'avez pas de copie du jeu ? <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - + <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> Buy the game - + Achetez le jeu. QObject <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> - + <br><b>Impossible de trouver Morrowind.ini</b><br><br>L'assistant d'installation requière de mettre à jour les paramètres de ce fichier.<br><br>Cliquez sur "Parcourir..." afin de spécifier manuellement son emplacement.<br> B&rowse... - + P&arcourir... Select configuration file - + Sélectionnez le fichier de configuration <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. - + L'archive <b>Morrowind.bsa</b> est manquante !<br>Assurez-vous que votre installation de Morrowind est complète. <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> - + <br><b>Il semble qu'une version plus à jour de Morrowind soit disponible.</b><br><br>Voulez-vous continuer malgré tout ?<br> Most recent Morrowind not detected - + Version la plus à jour de Morrowind non détectée Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. - + Sélectionnez un support %1 d'installation valide.<br><b>Aide</b> : Assurez-vous qu'il contienne au moins un fichier <b>.cab</b>. There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? - + Il semble qu'une version plus à jour de Morrowind soit disponible.<br><br>Voulez-vous continuer malgré tout ? Wizard::ComponentSelectionPage &Install - + &Installer &Skip - + &Passer Morrowind (installed) - + Morrowind (installé) Morrowind - + Morrowind Tribunal (installed) - + Tribunal (installé) Tribunal - + Tribunal Bloodmoon (installed) - + Bloodmoon (installé) Bloodmoon - + Bloodmoon About to install Tribunal after Bloodmoon - + À propos de l'installation de Tribunal après Bloodmoon <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> - + <html><head/><body><p><b>Vous vous apprêtez à installer l'extension Tribunal.</b></p><p>L'extension Bloodmoon est déjà installée sur votre ordinateur.</p><p>Néanmoins, il est recommandé d'installer Tribunal avant Bloodmoon.</p><p>Désirez-vous réinstaller Bloodmoon ?</p></body></html> Re-install &Bloodmoon - + Réinstaller &Bloodmoon Wizard::ConclusionPage <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> - + <html><head/><body><p>L'Assistant d'installation d'OpenMW a installé Morrowind sur votre ordinateur avec succès !</p></body></html> <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> - + <html><head/><body><p>L'Assistant d'installation d'OpenMW a modifié votre installation de Morrowind avec succès !</p></body></html> <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> - + <html><head/><body><p>L'assistant d'installation d'OpenMW a échoué l'installation de Morrowind.</p><p>Veuillez signaler les bogues que vous avez pu rencontrer sur notre. <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a> (en anglais).<br/>Assurez-vous d'inclure le journal (log) d'installation.</p><br/></body></html> Wizard::ExistingInstallationPage No existing installations detected - + Aucune installation existante détectée Error detecting Morrowind configuration - + Erreur lors de la détection du fichier de configuration de Morrowind Morrowind configuration file (*.ini) - + Fichier de configuration de Morrowind (*.ini) Select Morrowind.esm (located in Data Files) - + Sélectionnez Morrowind.esm (situé dans Data Files) Morrowind master file (Morrowind.esm) - + Fichier principal de Morrowind (Morrowind.esm) Error detecting Morrowind files - + Erreur lors de la détection des fichiers de Morrowind Wizard::InstallationPage <p>Attempting to install component %1.</p> - + <p>Tentative d'installation de la composante %1.</p> Attempting to install component %1. - + Tentative d'installation de la composante %1. %1 Installation - + Installation de %1 Select %1 installation media - + Sélectionnez le support d'installation de %1 <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> - + <p><br/><span style="color:red;"><b>Erreur : L'installation a été annulée par l'utilisateur.</b></span></p> <p>Detected old version of component Morrowind.</p> - + <p>Ancienne version de la composante Morrowind détectée.</p> Detected old version of component Morrowind. - + Ancienne version de la composante Morrowind détectée. Morrowind Installation - + Installation de Morrowind Installation finished - + Installation terminée Installation completed successfully! - + Installation complétée avec succès ! Installation failed! - + Échec de l'installation ! <p><br/><span style="color:red;"><b>Error: %1</b></p> - + <p><br/><span style="color:red;"><b>Erreur : %1</b></p> <p><span style="color:red;"><b>%1</b></p> - + <p><span style="color:red;"><b>%1</b></p> <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> - + <html><head/><body><p><b>L'assistant d'installation a rencontré une erreur.</b></p><p>L'erreur reportée est :</p><p>%1</p><p>Cliquez sur "Montrer les détails..." pour plus d'informations.</p></body></html> An error occurred - + Une erreur est survenue Wizard::InstallationTargetPage Error creating destination - + Erreur lors de la création de l'emplacement de l'installation <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - + <html><head/><body><p><b>Impossible de créer le dossier d'installation</b></p><p>Veuillez vous assurer que vous avez les bons droits d'accès puis réessayez, ou spécifiez une autre destination.</p></body></html> <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> - + <html><head/><body><p><b>Impossible d'écrire dans le dossier d'installation</b></p><p>Veuillez vous assurer que vous avez les bons droits d'accès puis réessayez, ou spécifiez une autre destination.</p></body></html> <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> - + <html><head/><body><p><b>Le dossier d'installation n'est pas vide.</b></p><p>Une installation de Morrowind est déjà présente dans ce dossier.</p><p>Veuillez spécifier un autre enmplacement pour l'installation, ou revenez en arrière et spécifiez que l'emplacement contient déjà une installation.</p></body></html> Insufficient permissions - + Droits d'accès insuffisants Destination not empty - + Emplacement non vide Select where to install Morrowind - + Sélectionnez où installer Morrowind Wizard::LanguageSelectionPage English - + Anglais French - + Français German - + Allemand Italian - + Italien Polish - + Polonais Russian - + Russe Spanish - + Espagnol Wizard::MainWizard OpenMW Wizard - + Assistant d'installation d'OpenMW Error opening Wizard log file - + Erreur lors de l'ouverture du journal de l'Assistant d'installation <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible d'ouvrir %1 en écriture.</b></p><p>Assurez-vous d'avoir les bons droits d'accès puis réessayez.</p></body></html> <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible d'ouvrir %1 en lecture.</b></p><p>Assurez-vous d'avoir les bons droits d'accès puis réessayez.</p></body></html> Error opening OpenMW configuration file - + Erreur lors de l'ouverture du fichier de configuration. Quit Wizard - + Quitter l'Assistant Are you sure you want to exit the Wizard? - + Êtes-vous sûr de vouloir quitter l'Assistant d'installation ? Error creating OpenMW configuration directory - + Erreur lors de la création du dossier de configuration d'OpenMW <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> - + <html><head/><body><p><b>Impossible de créer %1.</b></p><p>Assurez-vous d'avoir les bons droits d'accès puis réessayez.</p></body></html> Error writing OpenMW configuration file - + Erreur lors de l'écriture dans le fichier de configuration d'OpenMW. Wizard::UnshieldWorker Failed to open Morrowind configuration file! - + Échec de l'ouverture du fichier de configuration de Morrowind ! Opening %1 failed: %2. - + Échec lors de l'ouverture de %1 : %2. Failed to write Morrowind configuration file! - + Échec de l'ouverture du fichier de configuration de Morrowind ! Writing to %1 failed: %2. - + Échec lors de l'écriture dans %1 : %2. Installing: %1 - + Installation : %1 Installing: %1 directory - + Installation : dossier %1 Installation finished! - + Installation terminée ! Component parameter is invalid! - + Le paramètre d'une des composantes est invalide ! An invalid component parameter was supplied. - + Un paramètre invalide d'une composante a été fournis. Failed to find a valid archive containing %1.bsa! Retrying. - + Impossible de trouver une archive contenant %1.bsa ! Nouvel essai. Installing %1 - + Installation de %1 Installation media path not set! - + Support d'installation non défini ! The source path for %1 was not set. - + L'emplacement de %1 n'a pas été défini. Cannot create temporary directory! - + Impossible de créer un dossier temporaire ! Failed to create %1. - + Impossible de créer %1. Cannot move into temporary directory! - + Impossible d'accéder au dossier temporaire ! Failed to move into %1. - + Impossible d'accéder à %1. Moving installation files - + Déplacement des fichiers d'installation. Could not install directory! - + Impossible d'installer le dossier ! Installing %1 to %2 failed. - + Échec de l'installation de %1 dans %2. Could not install translation file! - + Impossible d'installer le fichier de localisation ! Failed to install *%1 files. - + Échec lors de l'installation du fichier %1. Could not install Morrowind data file! - + Impossible d'installer le fichier de données de Morrowind ! Failed to install %1. - + Échec lors de l'installation de %1. Could not install Morrowind configuration file! - + Impossible d'installer le fichier de configuration de Morrowind ! Installing: Sound directory - + Installation : Dossier des sons. Could not find Tribunal data file! - + Impossible d'installer le fichier de données de Tribunal ! Failed to find %1. - + Impossible de trouver %1. Could not find Tribunal patch file! - + Impossible de trouver le fichier de correctifs de Tribunal ! Could not find Bloodmoon data file! - + Impossible d'installer le fichier de données de Bloodmoon ! Updating Morrowind configuration file - + Mise à jour du fichier de configuration de Morrowind. %1 installation finished! - + Installation de %1 terminée ! Extracting: %1 - + Extraction : %1 Failed to open InstallShield Cabinet File. - + Impossible d'ouvrir le fichier InstallShield Cabinet. Opening %1 failed. - + Échec lors de l'ouverture de %1. Failed to extract %1. - + Échec lors de l'extraction de %1. Complete path: %1 - + Emplacement complet : %1. From 6e79064a57b89bfec0470fc6cfdb469dc651b05c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 12 Apr 2024 18:32:47 +0200 Subject: [PATCH 376/451] Fix editor oddities --- apps/opencs/model/world/infoselectwrapper.cpp | 44 ++++++------------- apps/opencs/model/world/infoselectwrapper.hpp | 5 --- .../model/world/nestedcoladapterimp.cpp | 5 ++- apps/openmw/mwdialogue/filter.cpp | 2 +- apps/openmw/mwdialogue/selectwrapper.cpp | 6 +-- components/esm3/dialoguecondition.cpp | 6 ++- components/esm3/dialoguecondition.hpp | 2 +- 7 files changed, 26 insertions(+), 44 deletions(-) diff --git a/apps/opencs/model/world/infoselectwrapper.cpp b/apps/opencs/model/world/infoselectwrapper.cpp index d1af341c04..2e9ed6e150 100644 --- a/apps/opencs/model/world/infoselectwrapper.cpp +++ b/apps/opencs/model/world/infoselectwrapper.cpp @@ -65,7 +65,7 @@ const char* CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[] = { "PC Endurance", "PC Personality", "PC Luck", - "PC Corpus", + "PC Corprus", "Weather", "PC Vampire", "Level", @@ -105,37 +105,21 @@ const char* CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[] = { nullptr, }; -const char* CSMWorld::ConstInfoSelectWrapper::ComparisonEnumStrings[] = { - "Boolean", - "Integer", - "Numeric", - nullptr, -}; - -// static functions - -std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ESM::DialogueCondition::Function name) +namespace { - if (name < ESM::DialogueCondition::Function_None) - return FunctionEnumStrings[name]; - else + std::string_view convertToString(ESM::DialogueCondition::Function name) + { + if (name < ESM::DialogueCondition::Function_None) + return CSMWorld::ConstInfoSelectWrapper::FunctionEnumStrings[name]; return "(Invalid Data: Function)"; -} + } -std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ESM::DialogueCondition::Comparison type) -{ - if (type < ESM::DialogueCondition::Comp_None) - return RelationEnumStrings[type]; - else + std::string_view convertToString(ESM::DialogueCondition::Comparison type) + { + if (type != ESM::DialogueCondition::Comp_None) + return CSMWorld::ConstInfoSelectWrapper::RelationEnumStrings[type - ESM::DialogueCondition::Comp_Eq]; return "(Invalid Data: Relation)"; -} - -std::string CSMWorld::ConstInfoSelectWrapper::convertToString(ComparisonType type) -{ - if (type < Comparison_None) - return ComparisonEnumStrings[type]; - else - return "(Invalid Data: Comparison)"; + } } // ConstInfoSelectWrapper @@ -269,7 +253,7 @@ void CSMWorld::ConstInfoSelectWrapper::updateComparisonType() case ESM::DialogueCondition::Function_SameFaction: case ESM::DialogueCondition::Function_Detected: case ESM::DialogueCondition::Function_Alarmed: - case ESM::DialogueCondition::Function_PcCorpus: + case ESM::DialogueCondition::Function_PcCorprus: case ESM::DialogueCondition::Function_PcVampire: case ESM::DialogueCondition::Function_Attacked: case ESM::DialogueCondition::Function_TalkedToPc: @@ -457,7 +441,7 @@ std::pair CSMWorld::ConstInfoSelectWrapper::getValidIntRange() const case ESM::DialogueCondition::Function_SameFaction: case ESM::DialogueCondition::Function_Detected: case ESM::DialogueCondition::Function_Alarmed: - case ESM::DialogueCondition::Function_PcCorpus: + case ESM::DialogueCondition::Function_PcCorprus: case ESM::DialogueCondition::Function_PcVampire: case ESM::DialogueCondition::Function_Attacked: case ESM::DialogueCondition::Function_TalkedToPc: diff --git a/apps/opencs/model/world/infoselectwrapper.hpp b/apps/opencs/model/world/infoselectwrapper.hpp index d8d108444f..b3b5abe462 100644 --- a/apps/opencs/model/world/infoselectwrapper.hpp +++ b/apps/opencs/model/world/infoselectwrapper.hpp @@ -25,11 +25,6 @@ namespace CSMWorld static const char* FunctionEnumStrings[]; static const char* RelationEnumStrings[]; - static const char* ComparisonEnumStrings[]; - - static std::string convertToString(ESM::DialogueCondition::Function name); - static std::string convertToString(ESM::DialogueCondition::Comparison type); - static std::string convertToString(ComparisonType type); ConstInfoSelectWrapper(const ESM::DialogueCondition& select); diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 86d38f9cd2..c844c5a18f 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -605,7 +605,7 @@ namespace CSMWorld } case 2: { - return infoSelectWrapper.getRelationType(); + return infoSelectWrapper.getRelationType() - ESM::DialogueCondition::Comp_Eq; } case 3: { @@ -643,7 +643,8 @@ namespace CSMWorld } case 2: // Relation { - infoSelectWrapper.setRelationType(static_cast(value.toInt())); + infoSelectWrapper.setRelationType( + static_cast(value.toInt() + ESM::DialogueCondition::Comp_Eq)); break; } case 3: // Value diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index acf87ccc61..295d690ce5 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -618,7 +618,7 @@ bool MWDialogue::Filter::getSelectStructBoolean(const SelectWrapper& select) con return player.getClass().getCreatureStats(player).hasBlightDisease(); - case ESM::DialogueCondition::Function_PcCorpus: + case ESM::DialogueCondition::Function_PcCorprus: return player.getClass() .getCreatureStats(player) diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index cc07ec8709..02c9d29b59 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -26,9 +26,9 @@ namespace return value1 < value2; case ESM::DialogueCondition::Comp_Le: return value1 <= value2; + default: + throw std::runtime_error("unknown compare type in dialogue info select"); } - - throw std::runtime_error("unknown compare type in dialogue info select"); } template @@ -226,7 +226,7 @@ MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const case ESM::DialogueCondition::Function_SameFaction: case ESM::DialogueCondition::Function_PcCommonDisease: case ESM::DialogueCondition::Function_PcBlightDisease: - case ESM::DialogueCondition::Function_PcCorpus: + case ESM::DialogueCondition::Function_PcCorprus: case ESM::DialogueCondition::Function_PcExpelled: case ESM::DialogueCondition::Function_PcVampire: case ESM::DialogueCondition::Function_TalkedToPc: diff --git a/components/esm3/dialoguecondition.cpp b/components/esm3/dialoguecondition.cpp index ba8f9586ce..a6a28307c2 100644 --- a/components/esm3/dialoguecondition.cpp +++ b/components/esm3/dialoguecondition.cpp @@ -45,7 +45,7 @@ namespace ESM return {}; } } - else if (rule[1] > '1' && rule[1] <= '9' || rule[1] >= 'A' && rule[1] <= 'C') + else if ((rule[1] > '1' && rule[1] <= '9') || (rule[1] >= 'A' && rule[1] <= 'C')) { if (rule.size() == 5) { @@ -134,6 +134,8 @@ namespace ESM void DialogueCondition::save(ESMWriter& esm) const { auto variant = std::visit([](auto value) { return ESM::Variant(value); }, mValue); + if (variant.getType() != VT_Float) + variant.setType(VT_Int); std::string rule; rule.reserve(5 + mVariable.size()); rule += static_cast(mIndex + '0'); @@ -189,7 +191,7 @@ namespace ESM start--; else start -= 2; - auto result = std::to_chars(start, end, mFunction); + auto result = std::to_chars(start, end, static_cast(mFunction)); if (result.ec != std::errc()) { Log(Debug::Error) << "Failed to save SCVR rule"; diff --git a/components/esm3/dialoguecondition.hpp b/components/esm3/dialoguecondition.hpp index 15ad4944f5..c06d50b601 100644 --- a/components/esm3/dialoguecondition.hpp +++ b/components/esm3/dialoguecondition.hpp @@ -75,7 +75,7 @@ namespace ESM Function_PcEndurance, Function_PcPersonality, Function_PcLuck, - Function_PcCorpus, + Function_PcCorprus, Function_Weather, Function_PcVampire, Function_Level, From f880ada633fc52b1006503054405dd595cbf1ed4 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 13 Apr 2024 00:06:24 +0300 Subject: [PATCH 377/451] Don't flip the water normal map twice --- apps/openmw/mwrender/water.cpp | 2 -- files/shaders/compatibility/water.frag | 1 - 2 files changed, 3 deletions(-) diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index a28ca0b7b7..cbc560d5ac 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -715,8 +715,6 @@ namespace MWRender osg::ref_ptr normalMap( new osg::Texture2D(mResourceSystem->getImageManager()->getImage("textures/omw/water_nm.png"))); - if (normalMap->getImage()) - normalMap->getImage()->flipVertical(); normalMap->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); normalMap->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); normalMap->setMaxAnisotropy(16); diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index d1324e01bd..749dcf27cd 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -94,7 +94,6 @@ uniform vec2 screenRes; void main(void) { vec2 UV = worldPos.xy / (8192.0*5.0) * 3.0; - UV.y *= -1.0; float shadow = unshadowedLightRatio(linearDepth); From 86b6eee62b2458e97b604f8946c5f2e2681dce59 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 25 Mar 2024 15:53:26 +0300 Subject: [PATCH 378/451] Improve hit detection emulation (#7900) --- apps/openmw/mwmechanics/combat.cpp | 62 +++++++++++++++++++----------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index b9852e1b41..5d283214a3 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -587,18 +587,20 @@ namespace MWMechanics MWBase::World* world = MWBase::Environment::get().getWorld(); const MWWorld::Store& store = world->getStore().get(); + // These GMSTs are not in degrees. They're tolerance angle sines multiplied by 90. + // With the default values of 60, the actual tolerance angles are roughly 41.8 degrees. + // Don't think too hard about it. In this place, thinking can cause permanent damage to your mental health. + const float fCombatAngleXY = store.find("fCombatAngleXY")->mValue.getFloat() / 90.f; + const float fCombatAngleZ = store.find("fCombatAngleZ")->mValue.getFloat() / 90.f; + const ESM::Position& posdata = actor.getRefData().getPosition(); const osg::Vec3f actorPos(posdata.asVec3()); - - // Morrowind uses body orientation or camera orientation if available - // The difference between that and this is subtle - osg::Quat actorRot - = osg::Quat(posdata.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(posdata.rot[2], osg::Vec3f(0, 0, -1)); - - const float fCombatAngleXY = store.find("fCombatAngleXY")->mValue.getFloat(); - const float fCombatAngleZ = store.find("fCombatAngleZ")->mValue.getFloat(); - const float combatAngleXYcos = std::cos(osg::DegreesToRadians(fCombatAngleXY)); - const float combatAngleZcos = std::cos(osg::DegreesToRadians(fCombatAngleZ)); + const osg::Vec3f actorDirXY = osg::Quat(posdata.rot[2], osg::Vec3(0, 0, -1)) * osg::Vec3f(0, 1, 0); + // Only the player can look up, apparently. + const float actorVerticalAngle = actor == getPlayer() ? -std::sin(posdata.rot[0]) : 0.f; + const float actorEyeLevel = world->getHalfExtents(actor, true).z() * 2.f * 0.85f; + const osg::Vec3f actorEyePos{ actorPos.x(), actorPos.y(), actorPos.z() + actorEyeLevel }; + const bool canMoveByZ = canActorMoveByZAxis(actor); // The player can target any active actor, non-playable actors only target their targets std::vector targets; @@ -612,26 +614,40 @@ namespace MWMechanics { if (actor == target || target.getClass().getCreatureStats(target).isDead()) continue; - float dist = getDistanceToBounds(actor, target); - osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); - osg::Vec3f dirToTarget = targetPos - actorPos; - if (dist >= reach || dist >= minDist || std::abs(dirToTarget.z()) >= reach) + const float dist = getDistanceToBounds(actor, target); + const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); + if (dist >= reach || dist >= minDist || std::abs(targetPos.z() - actorPos.z()) >= reach) continue; - dirToTarget.normalize(); + // Horizontal angle checks. + osg::Vec2f actorToTargetXY{ targetPos.x() - actorPos.x(), targetPos.y() - actorPos.y() }; + actorToTargetXY.normalize(); - // The idea is to use fCombatAngleXY and fCombatAngleZ as tolerance angles - // in XY and YZ planes of the coordinate system where the actor's orientation - // corresponds to (0, 1, 0) vector. This is not exactly what Morrowind does - // but Morrowind does something (even more) stupid here - osg::Vec3f hitDir = actorRot.inverse() * dirToTarget; - if (combatAngleXYcos * std::abs(hitDir.x()) > hitDir.y()) + // Use dot product to check if the target is behind first... + if (actorToTargetXY.x() * actorDirXY.x() + actorToTargetXY.y() * actorDirXY.y() <= 0.f) continue; - // Nice cliff racer hack Todd - if (combatAngleZcos * std::abs(hitDir.z()) > hitDir.y() && !MWMechanics::canActorMoveByZAxis(target)) + // And then perp dot product to calculate the hit angle sine. + // This gives us a horizontal hit range of [-asin(fCombatAngleXY / 90); asin(fCombatAngleXY / 90)] + if (std::abs(actorToTargetXY.x() * actorDirXY.y() - actorToTargetXY.y() * actorDirXY.x()) > fCombatAngleXY) continue; + // Vertical angle checks. Nice cliff racer hack, Todd. + if (!canMoveByZ) + { + // The idea is that the body should always be possible to hit. + // fCombatAngleZ is the tolerance for hitting the target's feet or head. + osg::Vec3f actorToTargetFeet = targetPos - actorEyePos; + osg::Vec3f actorToTargetHead = actorToTargetFeet; + actorToTargetFeet.normalize(); + actorToTargetHead.z() += world->getHalfExtents(target, true).z() * 2.f; + actorToTargetHead.normalize(); + + if (actorVerticalAngle - actorToTargetHead.z() > fCombatAngleZ + || actorVerticalAngle - actorToTargetFeet.z() < -fCombatAngleZ) + continue; + } + // Gotta use physics somehow! if (!world->getLOS(actor, target)) continue; From 1930bfeabb522781d5825bdee123a07216e62b13 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 13 Apr 2024 17:09:48 +0100 Subject: [PATCH 379/451] Support coloured terminal output on Windows First try the modern Windowsy way, where we can directly query if escape sequences will be processed. The function is available as far back as Windows 2000, but it just won't return the right flag until the Windows version is new enough. If that fails, fall back to the Unixy way, as not all colour-supporting terminal emulators for Windows use the Win32 API to declare that capability. The implementation isn't identical as isatty wasn't available without adding more headers, and we already have Windows.h in this file, so I might as well use the Win32 API instead of its POSIX-compatibility layer. --- components/debug/debugging.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index e9e50ff836..bfde558c85 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -256,9 +256,17 @@ namespace Debug private: static bool useColoredOutput() { - // Note: cmd.exe in Win10 should support ANSI colors, but in its own way. #if defined(_WIN32) - return 0; + if (getenv("NO_COLOR")) + return false; + + DWORD mode; + if (GetConsoleMode(GetStdHandle(STD_ERROR_HANDLE), &mode) && mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) + return true; + + // some console emulators may not use the Win32 API, so try the Unixy approach + char* term = getenv("TERM"); + return term && GetFileType(GetStdHandle(STD_ERROR_HANDLE)) == FILE_TYPE_CHAR; #else char* term = getenv("TERM"); bool useColor = term && !getenv("NO_COLOR") && isatty(fileno(stderr)); From a1438f65feccb3e7b2c58b2af99be86891b13bcc Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 30 Mar 2024 02:09:55 +0100 Subject: [PATCH 380/451] Set proper max tiles on initializing navmesh settings --- .../detournavigator/asyncnavmeshupdater.cpp | 11 +- components/detournavigator/makenavmesh.cpp | 26 +---- components/detournavigator/navmeshmanager.cpp | 2 +- components/detournavigator/settings.cpp | 103 +++++++++++++----- components/detournavigator/settings.hpp | 4 - files/settings-default.cfg | 2 +- 6 files changed, 81 insertions(+), 67 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 03f6062788..1ea7b63429 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -180,13 +180,10 @@ namespace DetourNavigator if (!playerTileChanged && changedTiles.empty()) return; - const int maxTiles - = std::min(mSettings.get().mMaxTilesNumber, navMeshCacheItem->lockConst()->getImpl().getParams()->maxTiles); - std::unique_lock lock(mMutex); if (playerTileChanged) - updateJobs(mWaiting, playerTile, maxTiles); + updateJobs(mWaiting, playerTile, mSettings.get().mMaxTilesNumber); for (const auto& [changedTile, changeType] : changedTiles) { @@ -221,7 +218,7 @@ namespace DetourNavigator lock.unlock(); if (playerTileChanged && mDbWorker != nullptr) - mDbWorker->updateJobs(playerTile, maxTiles); + mDbWorker->updateJobs(playerTile, mSettings.get().mMaxTilesNumber); } void AsyncNavMeshUpdater::wait(WaitConditionType waitConditionType, Loading::Listener* listener) @@ -376,10 +373,8 @@ namespace DetourNavigator return JobStatus::Done; const auto playerTile = *mPlayerTile.lockConst(); - const int maxTiles - = std::min(mSettings.get().mMaxTilesNumber, navMeshCacheItem->lockConst()->getImpl().getParams()->maxTiles); - if (!shouldAddTile(job.mChangedTile, playerTile, maxTiles)) + if (!shouldAddTile(job.mChangedTile, playerTile, mSettings.get().mMaxTilesNumber)) { Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player"; navMeshCacheItem->lock()->removeTile(job.mChangedTile); diff --git a/components/detournavigator/makenavmesh.cpp b/components/detournavigator/makenavmesh.cpp index d35ecf499d..e143bf1837 100644 --- a/components/detournavigator/makenavmesh.cpp +++ b/components/detournavigator/makenavmesh.cpp @@ -480,15 +480,6 @@ namespace DetourNavigator return true; } - template - unsigned long getMinValuableBitsNumber(const T value) - { - unsigned long power = 0; - while (power < sizeof(T) * 8 && (static_cast(1) << power) < value) - ++power; - return power; - } - std::pair getBoundsByZ( const RecastMesh& recastMesh, float agentHalfExtentsZ, const RecastSettings& settings) { @@ -528,10 +519,7 @@ namespace DetourNavigator return { minZ, maxZ }; } } -} // namespace DetourNavigator -namespace DetourNavigator -{ std::unique_ptr prepareNavMeshTileData(const RecastMesh& recastMesh, std::string_view worldspace, const TilePosition& tilePosition, const AgentBounds& agentBounds, const RecastSettings& settings) @@ -621,22 +609,12 @@ namespace DetourNavigator void initEmptyNavMesh(const Settings& settings, dtNavMesh& navMesh) { - // Max tiles and max polys affect how the tile IDs are caculated. - // There are 22 bits available for identifying a tile and a polygon. - const int polysAndTilesBits = 22; - const auto polysBits = getMinValuableBitsNumber(settings.mDetour.mMaxPolys); - - if (polysBits >= polysAndTilesBits) - throw InvalidArgument("Too many polygons per tile"); - - const auto tilesBits = polysAndTilesBits - polysBits; - dtNavMeshParams params; std::fill_n(params.orig, 3, 0.0f); params.tileWidth = settings.mRecast.mTileSize * settings.mRecast.mCellSize; params.tileHeight = settings.mRecast.mTileSize * settings.mRecast.mCellSize; - params.maxTiles = 1 << tilesBits; - params.maxPolys = 1 << polysBits; + params.maxTiles = settings.mMaxTilesNumber; + params.maxPolys = settings.mDetour.mMaxPolys; const auto status = navMesh.init(¶ms); diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index f4a82b850f..e6f831bb6e 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -181,7 +181,7 @@ namespace DetourNavigator { const auto locked = cached->lockConst(); const auto& navMesh = locked->getImpl(); - const auto maxTiles = std::min(mSettings.mMaxTilesNumber, navMesh.getParams()->maxTiles); + const int maxTiles = mSettings.mMaxTilesNumber; getTilesPositions(range, [&](const TilePosition& tile) { if (changedTiles.find(tile) != changedTiles.end()) return; diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index 0470c629e5..5e555050f7 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -3,41 +3,81 @@ #include #include +#include +#include +#include + namespace DetourNavigator { - RecastSettings makeRecastSettingsFromSettingsManager() + namespace { - RecastSettings result; - - result.mBorderSize = ::Settings::navigator().mBorderSize; - result.mCellHeight = ::Settings::navigator().mCellHeight; - result.mCellSize = ::Settings::navigator().mCellSize; - result.mDetailSampleDist = ::Settings::navigator().mDetailSampleDist; - result.mDetailSampleMaxError = ::Settings::navigator().mDetailSampleMaxError; - result.mMaxClimb = Constants::sStepSizeUp; - result.mMaxSimplificationError = ::Settings::navigator().mMaxSimplificationError; - result.mMaxSlope = Constants::sMaxSlope; - result.mRecastScaleFactor = ::Settings::navigator().mRecastScaleFactor; - result.mSwimHeightScale = 0; - result.mMaxEdgeLen = ::Settings::navigator().mMaxEdgeLen; - result.mMaxVertsPerPoly = ::Settings::navigator().mMaxVertsPerPoly; - result.mRegionMergeArea = ::Settings::navigator().mRegionMergeArea; - result.mRegionMinArea = ::Settings::navigator().mRegionMinArea; - result.mTileSize = ::Settings::navigator().mTileSize; + struct NavMeshLimits + { + int mMaxTiles; + int mMaxPolys; + }; - return result; - } + template + unsigned long getMinValuableBitsNumber(const T value) + { + unsigned long power = 0; + while (power < sizeof(T) * 8 && (static_cast(1) << power) < value) + ++power; + return power; + } - DetourSettings makeDetourSettingsFromSettingsManager() - { - DetourSettings result; + NavMeshLimits getNavMeshTileLimits(const DetourSettings& settings) + { + // Max tiles and max polys affect how the tile IDs are caculated. + // There are 22 bits available for identifying a tile and a polygon. + constexpr int polysAndTilesBits = 22; + const unsigned long polysBits = getMinValuableBitsNumber(settings.mMaxPolys); - result.mMaxNavMeshQueryNodes = ::Settings::navigator().mMaxNavMeshQueryNodes; - result.mMaxPolys = ::Settings::navigator().mMaxPolygonsPerTile; - result.mMaxPolygonPathSize = ::Settings::navigator().mMaxPolygonPathSize; - result.mMaxSmoothPathSize = ::Settings::navigator().mMaxSmoothPathSize; + if (polysBits >= polysAndTilesBits) + throw std::invalid_argument("Too many polygons per tile: " + std::to_string(settings.mMaxPolys)); - return result; + const unsigned long tilesBits = polysAndTilesBits - polysBits; + + return NavMeshLimits{ + .mMaxTiles = static_cast(1 << tilesBits), + .mMaxPolys = static_cast(1 << polysBits), + }; + } + + RecastSettings makeRecastSettingsFromSettingsManager() + { + RecastSettings result; + + result.mBorderSize = ::Settings::navigator().mBorderSize; + result.mCellHeight = ::Settings::navigator().mCellHeight; + result.mCellSize = ::Settings::navigator().mCellSize; + result.mDetailSampleDist = ::Settings::navigator().mDetailSampleDist; + result.mDetailSampleMaxError = ::Settings::navigator().mDetailSampleMaxError; + result.mMaxClimb = Constants::sStepSizeUp; + result.mMaxSimplificationError = ::Settings::navigator().mMaxSimplificationError; + result.mMaxSlope = Constants::sMaxSlope; + result.mRecastScaleFactor = ::Settings::navigator().mRecastScaleFactor; + result.mSwimHeightScale = 0; + result.mMaxEdgeLen = ::Settings::navigator().mMaxEdgeLen; + result.mMaxVertsPerPoly = ::Settings::navigator().mMaxVertsPerPoly; + result.mRegionMergeArea = ::Settings::navigator().mRegionMergeArea; + result.mRegionMinArea = ::Settings::navigator().mRegionMinArea; + result.mTileSize = ::Settings::navigator().mTileSize; + + return result; + } + + DetourSettings makeDetourSettingsFromSettingsManager() + { + DetourSettings result; + + result.mMaxNavMeshQueryNodes = ::Settings::navigator().mMaxNavMeshQueryNodes; + result.mMaxPolys = ::Settings::navigator().mMaxPolygonsPerTile; + result.mMaxPolygonPathSize = ::Settings::navigator().mMaxPolygonPathSize; + result.mMaxSmoothPathSize = ::Settings::navigator().mMaxSmoothPathSize; + + return result; + } } Settings makeSettingsFromSettingsManager() @@ -46,7 +86,12 @@ namespace DetourNavigator result.mRecast = makeRecastSettingsFromSettingsManager(); result.mDetour = makeDetourSettingsFromSettingsManager(); - result.mMaxTilesNumber = ::Settings::navigator().mMaxTilesNumber; + + const NavMeshLimits limits = getNavMeshTileLimits(result.mDetour); + + result.mDetour.mMaxPolys = limits.mMaxPolys; + + result.mMaxTilesNumber = std::min(limits.mMaxTiles, ::Settings::navigator().mMaxTilesNumber.get()); result.mWaitUntilMinDistanceToPlayer = ::Settings::navigator().mWaitUntilMinDistanceToPlayer; result.mAsyncNavMeshUpdaterThreads = ::Settings::navigator().mAsyncNavMeshUpdaterThreads; result.mMaxNavMeshTilesCacheSize = ::Settings::navigator().mMaxNavMeshTilesCacheSize; diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index 45bcf15dbf..1d1f6f5847 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -55,10 +55,6 @@ namespace DetourNavigator inline constexpr std::int64_t navMeshFormatVersion = 2; - RecastSettings makeRecastSettingsFromSettingsManager(); - - DetourSettings makeDetourSettingsFromSettingsManager(); - Settings makeSettingsFromSettingsManager(); } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 73331867a7..2f236bf55a 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -981,7 +981,7 @@ enable agents paths render = false enable recast mesh render = false # Max number of navmesh tiles (value >= 0) -max tiles number = 512 +max tiles number = 1024 # Min time duration for the same tile update in milliseconds (value >= 0) min update interval ms = 250 From 61c69c5563589b76b1857f0a9624f901e8f0ad69 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Apr 2024 15:35:35 +0200 Subject: [PATCH 381/451] Use proper prefix for CollisionShapeType --- components/detournavigator/debug.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/detournavigator/debug.cpp b/components/detournavigator/debug.cpp index 835f37f999..4dcd5e9857 100644 --- a/components/detournavigator/debug.cpp +++ b/components/detournavigator/debug.cpp @@ -79,13 +79,13 @@ namespace DetourNavigator switch (v) { case CollisionShapeType::Aabb: - return s << "AgentShapeType::Aabb"; + return s << "CollisionShapeType::Aabb"; case CollisionShapeType::RotatingBox: - return s << "AgentShapeType::RotatingBox"; + return s << "CollisionShapeType::RotatingBox"; case CollisionShapeType::Cylinder: - return s << "AgentShapeType::Cylinder"; + return s << "CollisionShapeType::Cylinder"; } - return s << "AgentShapeType::" << static_cast>(v); + return s << "CollisionShapeType::" << static_cast>(v); } std::ostream& operator<<(std::ostream& s, const AgentBounds& v) From d6f3d34f2f75078d8b897a89484dc5fcbc1ea546 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Apr 2024 15:40:42 +0200 Subject: [PATCH 382/451] Remove tiles present on navmesh but outside desired area --- .../detournavigator/navigator.cpp | 90 +++++++++++++++++++ .../detournavigator/navmeshcacheitem.hpp | 9 ++ components/detournavigator/navmeshmanager.cpp | 8 +- 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index b61a88662b..6dcade083a 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -1055,6 +1055,96 @@ namespace } } + TEST_P(DetourNavigatorUpdateTest, update_should_change_covered_area_when_player_moves_without_waiting_for_all) + { + Loading::Listener listener; + Settings settings = makeSettings(); + settings.mMaxTilesNumber = 1; + settings.mWaitUntilMinDistanceToPlayer = 1; + NavigatorImpl navigator(settings, nullptr); + const AgentBounds agentBounds{ CollisionShapeType::Aabb, { 29, 29, 66 } }; + ASSERT_TRUE(navigator.addAgent(agentBounds)); + + GetParam()(navigator); + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(3000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::requiredTilesPresent, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTile(4, 4); + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles; + } + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(6000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::requiredTilesPresent, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTile(8, 4); + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles; + } + } + + TEST_P(DetourNavigatorUpdateTest, update_should_change_covered_area_when_player_moves_with_db) + { + Loading::Listener listener; + Settings settings = makeSettings(); + settings.mMaxTilesNumber = 1; + settings.mWaitUntilMinDistanceToPlayer = 1; + NavigatorImpl navigator(settings, std::make_unique(":memory:", settings.mMaxDbFileSize)); + const AgentBounds agentBounds{ CollisionShapeType::Aabb, { 29, 29, 66 } }; + ASSERT_TRUE(navigator.addAgent(agentBounds)); + + GetParam()(navigator); + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(3000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::requiredTilesPresent, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTile(4, 4); + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles; + } + + { + auto updateGuard = navigator.makeUpdateGuard(); + navigator.update(osg::Vec3f(6000, 3000, 0), updateGuard.get()); + } + + navigator.wait(WaitConditionType::requiredTilesPresent, &listener); + + { + const auto navMesh = navigator.getNavMesh(agentBounds); + ASSERT_NE(navMesh, nullptr); + + const TilePosition expectedTile(8, 4); + const auto usedTiles = getUsedTiles(*navMesh->lockConst()); + EXPECT_THAT(usedTiles, Contains(expectedTile)) << usedTiles; + } + } + struct AddHeightfieldSurface { static constexpr std::size_t sSize = 65; diff --git a/components/detournavigator/navmeshcacheitem.hpp b/components/detournavigator/navmeshcacheitem.hpp index ec76b56a46..120a374195 100644 --- a/components/detournavigator/navmeshcacheitem.hpp +++ b/components/detournavigator/navmeshcacheitem.hpp @@ -131,6 +131,15 @@ namespace DetourNavigator function(position, tile.mVersion, *meshTile); } + template + void forEachTilePosition(Function&& function) const + { + for (const auto& [position, tile] : mUsedTiles) + function(position); + for (const TilePosition& position : mEmptyTiles) + function(position); + } + private: struct Tile { diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index e6f831bb6e..ea20f8bc34 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -13,8 +13,6 @@ #include -#include - namespace { /// Safely reset shared_ptr with definite underlying object destrutor call. @@ -179,9 +177,9 @@ namespace DetourNavigator { std::map tilesToPost = changedTiles; { + const int maxTiles = mSettings.mMaxTilesNumber; const auto locked = cached->lockConst(); const auto& navMesh = locked->getImpl(); - const int maxTiles = mSettings.mMaxTilesNumber; getTilesPositions(range, [&](const TilePosition& tile) { if (changedTiles.find(tile) != changedTiles.end()) return; @@ -192,6 +190,10 @@ namespace DetourNavigator else if (!shouldAdd && presentInNavMesh) tilesToPost.emplace(tile, ChangeType::mixed); }); + locked->forEachTilePosition([&](const TilePosition& tile) { + if (!shouldAddTile(tile, playerTile, maxTiles)) + tilesToPost.emplace(tile, ChangeType::remove); + }); } mAsyncNavMeshUpdater.post(agentBounds, cached, playerTile, mWorldspace, tilesToPost); Log(Debug::Debug) << "Cache update posted for agent=" << agentBounds << " playerTile=" << playerTile From 17bd571a65e5d445cea4b84eb0309fa0976f1da8 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 12 Apr 2024 18:37:56 +0200 Subject: [PATCH 383/451] Do not repost failed jobs Failures should not happen except for some weird corner cases. Retrying is unlikely to help in such situation. --- .../detournavigator/asyncnavmeshupdater.cpp | 23 ++----------------- .../detournavigator/asyncnavmeshupdater.hpp | 2 -- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 1ea7b63429..bb04c2af07 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -343,7 +343,8 @@ namespace DetourNavigator removeJob(job); break; case JobStatus::Fail: - repost(job); + unlockTile(job->mId, job->mAgentBounds, job->mChangedTile); + removeJob(job); break; case JobStatus::MemoryCacheMiss: { @@ -608,26 +609,6 @@ namespace DetourNavigator writeToFile(shared->lockConst()->getImpl(), mSettings.get().mNavMeshPathPrefix, navMeshRevision); } - void AsyncNavMeshUpdater::repost(JobIt job) - { - unlockTile(job->mId, job->mAgentBounds, job->mChangedTile); - - if (mShouldStop || job->mTryNumber > 2) - return; - - const std::lock_guard lock(mMutex); - - if (mPushed.emplace(job->mAgentBounds, job->mChangedTile).second) - { - ++job->mTryNumber; - insertPrioritizedJob(job, mWaiting); - mHasJob.notify_all(); - return; - } - - mJobs.erase(job); - } - bool AsyncNavMeshUpdater::lockTile( std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile) { diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 9b95d4f4b3..09119556cd 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -197,8 +197,6 @@ namespace DetourNavigator void writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const; - void repost(JobIt job); - bool lockTile(std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile); void unlockTile(std::size_t jobId, const AgentBounds& agentBounds, const TilePosition& changedTile); From 50f4471750d934c238244ae5e408d69bf681dc0c Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Apr 2024 01:10:48 +0200 Subject: [PATCH 384/451] Use R-tree for dynamic priority of navmesh async job --- .../detournavigator/asyncnavmeshupdater.cpp | 271 +++++++++++++++- .../detournavigator/asyncnavmeshupdater.cpp | 307 ++++++++++++------ .../detournavigator/asyncnavmeshupdater.hpp | 75 ++++- components/detournavigator/changetype.hpp | 10 +- components/detournavigator/debug.cpp | 2 - components/detournavigator/navmeshmanager.cpp | 2 +- components/detournavigator/stats.cpp | 8 +- components/detournavigator/stats.hpp | 11 +- .../tilecachedrecastmeshmanager.cpp | 3 +- components/resource/stats.cpp | 11 +- 10 files changed, 573 insertions(+), 127 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp index bc1288f5f6..51ab37b123 100644 --- a/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp +++ b/apps/openmw_test_suite/detournavigator/asyncnavmeshupdater.cpp @@ -267,7 +267,6 @@ namespace updater.wait(WaitConditionType::allJobsDone, &mListener); updater.stop(); const std::set present{ - TilePosition(-2, 0), TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(-1, 1), @@ -278,6 +277,7 @@ namespace TilePosition(0, 2), TilePosition(1, -1), TilePosition(1, 0), + TilePosition(1, 1), }; for (int x = -5; x <= 5; ++x) for (int y = -5; y <= 5; ++y) @@ -336,4 +336,273 @@ namespace EXPECT_EQ(tile->mTileId, 2); EXPECT_EQ(tile->mVersion, navMeshFormatVersion); } + + TEST_F(DetourNavigatorAsyncNavMeshUpdaterTest, repeated_tile_updates_should_be_delayed) + { + mRecastMeshManager.setWorldspace(mWorldspace, nullptr); + + mSettings.mMaxTilesNumber = 9; + mSettings.mMinUpdateInterval = std::chrono::milliseconds(250); + + AsyncNavMeshUpdater updater(mSettings, mRecastMeshManager, mOffMeshConnectionsManager, nullptr); + const auto navMeshCacheItem = std::make_shared(1, mSettings); + + std::map changedTiles; + + for (int x = -3; x <= 3; ++x) + for (int y = -3; y <= 3; ++y) + changedTiles.emplace(TilePosition{ x, y }, ChangeType::update); + + updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + + updater.wait(WaitConditionType::allJobsDone, &mListener); + + { + const AsyncNavMeshUpdaterStats stats = updater.getStats(); + EXPECT_EQ(stats.mJobs, 0); + EXPECT_EQ(stats.mWaiting.mDelayed, 0); + } + + updater.post(mAgentBounds, navMeshCacheItem, mPlayerTile, mWorldspace, changedTiles); + + { + const AsyncNavMeshUpdaterStats stats = updater.getStats(); + EXPECT_EQ(stats.mJobs, 49); + EXPECT_EQ(stats.mWaiting.mDelayed, 49); + } + + updater.wait(WaitConditionType::allJobsDone, &mListener); + + { + const AsyncNavMeshUpdaterStats stats = updater.getStats(); + EXPECT_EQ(stats.mJobs, 0); + EXPECT_EQ(stats.mWaiting.mDelayed, 0); + } + } + + struct DetourNavigatorSpatialJobQueueTest : Test + { + const AgentBounds mAgentBounds{ CollisionShapeType::Aabb, osg::Vec3f(1, 1, 1) }; + const std::shared_ptr mNavMeshCacheItemPtr; + const std::weak_ptr mNavMeshCacheItem = mNavMeshCacheItemPtr; + const std::string_view mWorldspace = "worldspace"; + const TilePosition mChangedTile{ 0, 0 }; + const std::chrono::steady_clock::time_point mProcessTime{}; + const TilePosition mPlayerTile{ 0, 0 }; + const int mMaxTiles = 9; + }; + + TEST_F(DetourNavigatorSpatialJobQueueTest, should_store_multiple_jobs_per_tile) + { + std::list jobs; + SpatialJobQueue queue; + + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, "worldspace1", mChangedTile, + ChangeType::remove, mProcessTime)); + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, "worldspace2", mChangedTile, + ChangeType::update, mProcessTime)); + + ASSERT_EQ(queue.size(), 2); + + const auto job1 = queue.pop(mChangedTile); + ASSERT_TRUE(job1.has_value()); + EXPECT_EQ((*job1)->mWorldspace, "worldspace1"); + + const auto job2 = queue.pop(mChangedTile); + ASSERT_TRUE(job2.has_value()); + EXPECT_EQ((*job2)->mWorldspace, "worldspace2"); + + EXPECT_EQ(queue.size(), 0); + } + + struct DetourNavigatorJobQueueTest : DetourNavigatorSpatialJobQueueTest + { + }; + + TEST_F(DetourNavigatorJobQueueTest, pop_should_return_nullptr_from_empty) + { + JobQueue queue; + ASSERT_FALSE(queue.hasJob()); + ASSERT_FALSE(queue.pop(mPlayerTile).has_value()); + } + + TEST_F(DetourNavigatorJobQueueTest, push_on_change_type_remove_should_add_to_removing) + { + const std::chrono::steady_clock::time_point processTime{}; + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::remove, processTime); + + JobQueue queue; + queue.push(job); + + EXPECT_EQ(queue.getStats().mRemoving, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, pop_should_return_last_removing) + { + std::list jobs; + JobQueue queue; + + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(0, 0), + ChangeType::remove, mProcessTime)); + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(1, 0), + ChangeType::remove, mProcessTime)); + + ASSERT_TRUE(queue.hasJob()); + const auto job = queue.pop(mPlayerTile); + ASSERT_TRUE(job.has_value()); + EXPECT_EQ((*job)->mChangedTile, TilePosition(1, 0)); + } + + TEST_F(DetourNavigatorJobQueueTest, push_on_change_type_not_remove_should_add_to_updating) + { + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, mProcessTime); + + JobQueue queue; + queue.push(job); + + EXPECT_EQ(queue.getStats().mUpdating, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, pop_should_return_nearest_to_player_tile) + { + std::list jobs; + + JobQueue queue; + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(0, 0), + ChangeType::update, mProcessTime)); + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(1, 0), + ChangeType::update, mProcessTime)); + + ASSERT_TRUE(queue.hasJob()); + const auto job = queue.pop(TilePosition(1, 0)); + ASSERT_TRUE(job.has_value()); + EXPECT_EQ((*job)->mChangedTile, TilePosition(1, 0)); + } + + TEST_F(DetourNavigatorJobQueueTest, push_on_processing_time_more_than_now_should_add_to_delayed) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime); + + JobQueue queue; + queue.push(job, now); + + EXPECT_EQ(queue.getStats().mDelayed, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, pop_should_return_when_delayed_job_is_ready) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime); + + JobQueue queue; + queue.push(job, now); + + ASSERT_FALSE(queue.hasJob(now)); + ASSERT_FALSE(queue.pop(mPlayerTile, now).has_value()); + + ASSERT_TRUE(queue.hasJob(processTime)); + EXPECT_TRUE(queue.pop(mPlayerTile, processTime).has_value()); + } + + TEST_F(DetourNavigatorJobQueueTest, update_should_move_ready_delayed_to_updating) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime); + + JobQueue queue; + queue.push(job, now); + + ASSERT_EQ(queue.getStats().mDelayed, 1); + + queue.update(mPlayerTile, mMaxTiles, processTime); + + EXPECT_EQ(queue.getStats().mDelayed, 0); + EXPECT_EQ(queue.getStats().mUpdating, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, update_should_move_ready_delayed_to_removing_when_out_of_range) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt job = jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, processTime); + + JobQueue queue; + queue.push(job, now); + + ASSERT_EQ(queue.getStats().mDelayed, 1); + + queue.update(TilePosition(10, 10), mMaxTiles, processTime); + + EXPECT_EQ(queue.getStats().mDelayed, 0); + EXPECT_EQ(queue.getStats().mRemoving, 1); + EXPECT_EQ(job->mChangeType, ChangeType::remove); + } + + TEST_F(DetourNavigatorJobQueueTest, update_should_move_updating_to_removing_when_out_of_range) + { + std::list jobs; + + JobQueue queue; + queue.push(jobs.emplace( + jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, mChangedTile, ChangeType::update, mProcessTime)); + queue.push(jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(10, 10), + ChangeType::update, mProcessTime)); + + ASSERT_EQ(queue.getStats().mUpdating, 2); + + queue.update(TilePosition(10, 10), mMaxTiles); + + EXPECT_EQ(queue.getStats().mUpdating, 1); + EXPECT_EQ(queue.getStats().mRemoving, 1); + } + + TEST_F(DetourNavigatorJobQueueTest, clear_should_remove_all) + { + const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + const std::chrono::steady_clock::time_point processTime = now + std::chrono::seconds(1); + + std::list jobs; + const JobIt removing = jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, + TilePosition(0, 0), ChangeType::remove, mProcessTime); + const JobIt updating = jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, + TilePosition(1, 0), ChangeType::update, mProcessTime); + const JobIt delayed = jobs.emplace(jobs.end(), mAgentBounds, mNavMeshCacheItem, mWorldspace, TilePosition(2, 0), + ChangeType::update, processTime); + + JobQueue queue; + queue.push(removing); + queue.push(updating); + queue.push(delayed, now); + + ASSERT_EQ(queue.getStats().mRemoving, 1); + ASSERT_EQ(queue.getStats().mUpdating, 1); + ASSERT_EQ(queue.getStats().mDelayed, 1); + + queue.clear(); + + EXPECT_EQ(queue.getStats().mRemoving, 0); + EXPECT_EQ(queue.getStats().mUpdating, 0); + EXPECT_EQ(queue.getStats().mDelayed, 0); + } } diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index bb04c2af07..fe6d0625f5 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -16,8 +16,9 @@ #include +#include + #include -#include #include #include #include @@ -49,40 +50,6 @@ namespace DetourNavigator return false; } - auto getPriority(const Job& job) noexcept - { - return std::make_tuple(-static_cast>(job.mState), job.mProcessTime, - job.mChangeType, job.mTryNumber, job.mDistanceToPlayer, job.mDistanceToOrigin); - } - - struct LessByJobPriority - { - bool operator()(JobIt lhs, JobIt rhs) const noexcept { return getPriority(*lhs) < getPriority(*rhs); } - }; - - void insertPrioritizedJob(JobIt job, std::deque& queue) - { - const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobPriority{}); - queue.insert(it, job); - } - - auto getDbPriority(const Job& job) noexcept - { - return std::make_tuple(static_cast>(job.mState), job.mChangeType, - job.mDistanceToPlayer, job.mDistanceToOrigin); - } - - struct LessByJobDbPriority - { - bool operator()(JobIt lhs, JobIt rhs) const noexcept { return getDbPriority(*lhs) < getDbPriority(*rhs); } - }; - - void insertPrioritizedDbJob(JobIt job, std::deque& queue) - { - const auto it = std::upper_bound(queue.begin(), queue.end(), job, LessByJobDbPriority{}); - queue.insert(it, job); - } - auto getAgentAndTile(const Job& job) noexcept { return std::make_tuple(job.mAgentBounds, job.mChangedTile); @@ -97,16 +64,6 @@ namespace DetourNavigator settings.mRecast, settings.mWriteToNavMeshDb); } - void updateJobs(std::deque& jobs, TilePosition playerTile, int maxTiles) - { - for (JobIt job : jobs) - { - job->mDistanceToPlayer = getManhattanDistance(job->mChangedTile, playerTile); - if (!shouldAddTile(job->mChangedTile, playerTile, maxTiles)) - job->mChangeType = ChangeType::remove; - } - } - std::size_t getNextJobId() { static std::atomic_size_t nextJobId{ 1 }; @@ -134,7 +91,7 @@ namespace DetourNavigator } Job::Job(const AgentBounds& agentBounds, std::weak_ptr navMeshCacheItem, - std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, + std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, std::chrono::steady_clock::time_point processTime) : mId(getNextJobId()) , mAgentBounds(agentBounds) @@ -143,11 +100,148 @@ namespace DetourNavigator , mChangedTile(changedTile) , mProcessTime(processTime) , mChangeType(changeType) - , mDistanceToPlayer(distanceToPlayer) - , mDistanceToOrigin(getManhattanDistance(changedTile, TilePosition{ 0, 0 })) { } + void SpatialJobQueue::clear() + { + mValues.clear(); + mIndex.clear(); + mSize = 0; + } + + void SpatialJobQueue::push(JobIt job) + { + auto it = mValues.find(job->mChangedTile); + + if (it == mValues.end()) + { + it = mValues.emplace_hint(it, job->mChangedTile, std::deque()); + mIndex.insert(IndexValue(IndexPoint(job->mChangedTile.x(), job->mChangedTile.y()), it)); + } + + it->second.push_back(job); + + ++mSize; + } + + std::optional SpatialJobQueue::pop(TilePosition playerTile) + { + const IndexPoint point(playerTile.x(), playerTile.y()); + const auto it = mIndex.qbegin(boost::geometry::index::nearest(point, 1)); + + if (it == mIndex.qend()) + return std::nullopt; + + const UpdatingMap::iterator mapIt = it->second; + std::deque& tileJobs = mapIt->second; + JobIt result = tileJobs.front(); + tileJobs.pop_front(); + + --mSize; + + if (tileJobs.empty()) + { + mValues.erase(mapIt); + mIndex.remove(*it); + } + + return result; + } + + void SpatialJobQueue::update(TilePosition playerTile, int maxTiles, std::vector& removing) + { + for (auto it = mValues.begin(); it != mValues.end();) + { + if (shouldAddTile(it->first, playerTile, maxTiles)) + { + ++it; + continue; + } + + for (JobIt job : it->second) + { + job->mChangeType = ChangeType::remove; + removing.push_back(job); + } + + mSize -= it->second.size(); + mIndex.remove(IndexValue(IndexPoint(it->first.x(), it->first.y()), it)); + it = mValues.erase(it); + } + } + + bool JobQueue::hasJob(std::chrono::steady_clock::time_point now) const + { + return !mRemoving.empty() || mUpdating.size() > 0 + || (!mDelayed.empty() && mDelayed.front()->mProcessTime <= now); + } + + void JobQueue::clear() + { + mRemoving.clear(); + mDelayed.clear(); + mUpdating.clear(); + } + + void JobQueue::push(JobIt job, std::chrono::steady_clock::time_point now) + { + if (job->mProcessTime > now) + { + mDelayed.push_back(job); + return; + } + + if (job->mChangeType == ChangeType::remove) + { + mRemoving.push_back(job); + return; + } + + mUpdating.push(job); + } + + std::optional JobQueue::pop(TilePosition playerTile, std::chrono::steady_clock::time_point now) + { + if (!mRemoving.empty()) + { + const JobIt result = mRemoving.back(); + mRemoving.pop_back(); + return result; + } + + if (const std::optional result = mUpdating.pop(playerTile)) + return result; + + if (mDelayed.empty() || mDelayed.front()->mProcessTime > now) + return std::nullopt; + + const JobIt result = mDelayed.front(); + mDelayed.pop_front(); + return result; + } + + void JobQueue::update(TilePosition playerTile, int maxTiles, std::chrono::steady_clock::time_point now) + { + mUpdating.update(playerTile, maxTiles, mRemoving); + + while (!mDelayed.empty() && mDelayed.front()->mProcessTime <= now) + { + const JobIt job = mDelayed.front(); + mDelayed.pop_front(); + + if (shouldAddTile(job->mChangedTile, playerTile, maxTiles)) + { + mUpdating.push(job); + } + else + { + job->mChangeType = ChangeType::remove; + mRemoving.push_back(job); + } + } + } + AsyncNavMeshUpdater::AsyncNavMeshUpdater(const Settings& settings, TileCachedRecastMeshManager& recastMeshManager, OffMeshConnectionsManager& offMeshConnectionsManager, std::unique_ptr&& db) : mSettings(settings) @@ -183,42 +277,44 @@ namespace DetourNavigator std::unique_lock lock(mMutex); if (playerTileChanged) - updateJobs(mWaiting, playerTile, mSettings.get().mMaxTilesNumber); + { + Log(Debug::Debug) << "Player tile has been changed to " << playerTile; + mWaiting.update(playerTile, mSettings.get().mMaxTilesNumber); + } for (const auto& [changedTile, changeType] : changedTiles) { if (mPushed.emplace(agentBounds, changedTile).second) { - const auto processTime = changeType == ChangeType::update - ? mLastUpdates[std::tie(agentBounds, changedTile)] + mSettings.get().mMinUpdateInterval - : std::chrono::steady_clock::time_point(); - - const JobIt it = mJobs.emplace(mJobs.end(), agentBounds, navMeshCacheItem, worldspace, changedTile, - changeType, getManhattanDistance(changedTile, playerTile), processTime); + const auto processTime = [&, changedTile = changedTile, changeType = changeType] { + if (changeType != ChangeType::update) + return std::chrono::steady_clock::time_point(); + const auto lastUpdate = mLastUpdates.find(std::tie(agentBounds, changedTile)); + if (lastUpdate == mLastUpdates.end()) + return std::chrono::steady_clock::time_point(); + return lastUpdate->second + mSettings.get().mMinUpdateInterval; + }(); + + const JobIt it = mJobs.emplace( + mJobs.end(), agentBounds, navMeshCacheItem, worldspace, changedTile, changeType, processTime); Log(Debug::Debug) << "Post job " << it->mId << " for agent=(" << it->mAgentBounds << ")" - << " changedTile=(" << it->mChangedTile << ") " + << " changedTile=(" << it->mChangedTile << ")" << " changeType=" << it->mChangeType; - if (playerTileChanged) - mWaiting.push_back(it); - else - insertPrioritizedJob(it, mWaiting); + mWaiting.push(it); } } - if (playerTileChanged) - std::sort(mWaiting.begin(), mWaiting.end(), LessByJobPriority{}); - Log(Debug::Debug) << "Posted " << mJobs.size() << " navigator jobs"; - if (!mWaiting.empty()) + if (mWaiting.hasJob()) mHasJob.notify_all(); lock.unlock(); if (playerTileChanged && mDbWorker != nullptr) - mDbWorker->updateJobs(playerTile, mSettings.get().mMaxTilesNumber); + mDbWorker->update(playerTile); } void AsyncNavMeshUpdater::wait(WaitConditionType waitConditionType, Loading::Listener* listener) @@ -310,7 +406,7 @@ namespace DetourNavigator { const std::lock_guard lock(mMutex); result.mJobs = mJobs.size(); - result.mWaiting = mWaiting.size(); + result.mWaiting = mWaiting.getStats(); result.mPushed = mPushed.size(); } result.mProcessing = mProcessingTiles.lockConst()->size(); @@ -332,7 +428,8 @@ namespace DetourNavigator if (JobIt job = getNextJob(); job != mJobs.end()) { const JobStatus status = processJob(*job); - Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status; + Log(Debug::Debug) << "Processed job " << job->mId << " with status=" << status + << " changeType=" << job->mChangeType; switch (status) { case JobStatus::Done: @@ -366,7 +463,9 @@ namespace DetourNavigator JobStatus AsyncNavMeshUpdater::processJob(Job& job) { - Log(Debug::Debug) << "Processing job " << job.mId << " by thread=" << std::this_thread::get_id(); + Log(Debug::Debug) << "Processing job " << job.mId << " for agent=(" << job.mAgentBounds << ")" + << " changedTile=(" << job.mChangedTile << ")" + << " changeType=" << job.mChangeType << " by thread=" << std::this_thread::get_id(); const auto navMeshCacheItem = job.mNavMeshCacheItem.lock(); @@ -378,6 +477,7 @@ namespace DetourNavigator if (!shouldAddTile(job.mChangedTile, playerTile, mSettings.get().mMaxTilesNumber)) { Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player"; + job.mChangeType = ChangeType::remove; navMeshCacheItem->lock()->removeTile(job.mChangedTile); return JobStatus::Done; } @@ -545,9 +645,8 @@ namespace DetourNavigator bool shouldStop = false; const auto hasJob = [&] { - shouldStop = mShouldStop; - return shouldStop - || (!mWaiting.empty() && mWaiting.front()->mProcessTime <= std::chrono::steady_clock::now()); + shouldStop = mShouldStop.load(); + return shouldStop || mWaiting.hasJob(); }; if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) @@ -560,9 +659,15 @@ namespace DetourNavigator if (shouldStop) return mJobs.end(); - const JobIt job = mWaiting.front(); + const TilePosition playerTile = *mPlayerTile.lockConst(); + + JobIt job = mJobs.end(); - mWaiting.pop_front(); + if (const std::optional nextJob = mWaiting.pop(playerTile)) + job = *nextJob; + + if (job == mJobs.end()) + return job; Log(Debug::Debug) << "Pop job " << job->mId << " by thread=" << std::this_thread::get_id(); @@ -571,9 +676,9 @@ namespace DetourNavigator if (!lockTile(job->mId, job->mAgentBounds, job->mChangedTile)) { - Log(Debug::Debug) << "Failed to lock tile by job " << job->mId << " try=" << job->mTryNumber; - ++job->mTryNumber; - insertPrioritizedJob(job, mWaiting); + Log(Debug::Debug) << "Failed to lock tile by job " << job->mId; + job->mProcessTime = std::chrono::steady_clock::now() + mSettings.get().mMinUpdateInterval; + mWaiting.push(job); return mJobs.end(); } @@ -653,7 +758,7 @@ namespace DetourNavigator { Log(Debug::Debug) << "Enqueueing job " << job->mId << " by thread=" << std::this_thread::get_id(); const std::lock_guard lock(mMutex); - insertPrioritizedJob(job, mWaiting); + mWaiting.push(job); mHasJob.notify_all(); } @@ -667,40 +772,47 @@ namespace DetourNavigator void DbJobQueue::push(JobIt job) { const std::lock_guard lock(mMutex); - insertPrioritizedDbJob(job, mJobs); if (isWritingDbJob(*job)) - ++mWritingJobs; + mWriting.push_back(job); else - ++mReadingJobs; + mReading.push(job); mHasJob.notify_all(); } std::optional DbJobQueue::pop() { std::unique_lock lock(mMutex); - mHasJob.wait(lock, [&] { return mShouldStop || !mJobs.empty(); }); - if (mJobs.empty()) + + const auto hasJob = [&] { return mShouldStop || mReading.size() > 0 || mWriting.size() > 0; }; + + mHasJob.wait(lock, hasJob); + + if (mShouldStop) return std::nullopt; - const JobIt job = mJobs.front(); - mJobs.pop_front(); - if (isWritingDbJob(*job)) - --mWritingJobs; - else - --mReadingJobs; + + if (const std::optional job = mReading.pop(mPlayerTile)) + return job; + + if (mWriting.empty()) + return std::nullopt; + + const JobIt job = mWriting.front(); + mWriting.pop_front(); + return job; } - void DbJobQueue::update(TilePosition playerTile, int maxTiles) + void DbJobQueue::update(TilePosition playerTile) { const std::lock_guard lock(mMutex); - updateJobs(mJobs, playerTile, maxTiles); - std::sort(mJobs.begin(), mJobs.end(), LessByJobDbPriority{}); + mPlayerTile = playerTile; } void DbJobQueue::stop() { const std::lock_guard lock(mMutex); - mJobs.clear(); + mReading.clear(); + mWriting.clear(); mShouldStop = true; mHasJob.notify_all(); } @@ -708,7 +820,10 @@ namespace DetourNavigator DbJobQueueStats DbJobQueue::getStats() const { const std::lock_guard lock(mMutex); - return DbJobQueueStats{ .mWritingJobs = mWritingJobs, .mReadingJobs = mReadingJobs }; + return DbJobQueueStats{ + .mReadingJobs = mReading.size(), + .mWritingJobs = mWriting.size(), + }; } DbWorker::DbWorker(AsyncNavMeshUpdater& updater, std::unique_ptr&& db, TileVersion version, @@ -737,8 +852,10 @@ namespace DetourNavigator DbWorkerStats DbWorker::getStats() const { - return DbWorkerStats{ .mJobs = mQueue.getStats(), - .mGetTileCount = mGetTileCount.load(std::memory_order_relaxed) }; + return DbWorkerStats{ + .mJobs = mQueue.getStats(), + .mGetTileCount = mGetTileCount.load(std::memory_order_relaxed), + }; } void DbWorker::stop() diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 09119556cd..f3d624a8a6 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -14,6 +14,9 @@ #include "tileposition.hpp" #include "waitconditiontype.hpp" +#include +#include + #include #include #include @@ -49,11 +52,8 @@ namespace DetourNavigator const std::weak_ptr mNavMeshCacheItem; const std::string mWorldspace; const TilePosition mChangedTile; - const std::chrono::steady_clock::time_point mProcessTime; - unsigned mTryNumber = 0; + std::chrono::steady_clock::time_point mProcessTime; ChangeType mChangeType; - int mDistanceToPlayer; - const int mDistanceToOrigin; JobState mState = JobState::Initial; std::vector mInput; std::shared_ptr mRecastMesh; @@ -61,12 +61,65 @@ namespace DetourNavigator std::unique_ptr mGeneratedNavMeshData; Job(const AgentBounds& agentBounds, std::weak_ptr navMeshCacheItem, - std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, int distanceToPlayer, + std::string_view worldspace, const TilePosition& changedTile, ChangeType changeType, std::chrono::steady_clock::time_point processTime); }; using JobIt = std::list::iterator; + class SpatialJobQueue + { + public: + std::size_t size() const { return mSize; } + + void clear(); + + void push(JobIt job); + + std::optional pop(TilePosition playerTile); + + void update(TilePosition playerTile, int maxTiles, std::vector& removing); + + private: + using IndexPoint = boost::geometry::model::point; + using UpdatingMap = std::map>; + using IndexValue = std::pair; + + std::size_t mSize = 0; + UpdatingMap mValues; + boost::geometry::index::rtree> mIndex; + }; + + class JobQueue + { + public: + JobQueueStats getStats() const + { + return JobQueueStats{ + .mRemoving = mRemoving.size(), + .mUpdating = mUpdating.size(), + .mDelayed = mDelayed.size(), + }; + } + + bool hasJob(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) const; + + void clear(); + + void push(JobIt job, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()); + + std::optional pop( + TilePosition playerTile, std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()); + + void update(TilePosition playerTile, int maxTiles, + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()); + + private: + std::vector mRemoving; + SpatialJobQueue mUpdating; + std::deque mDelayed; + }; + enum class JobStatus { Done, @@ -83,7 +136,7 @@ namespace DetourNavigator std::optional pop(); - void update(TilePosition playerTile, int maxTiles); + void update(TilePosition playerTile); void stop(); @@ -92,10 +145,10 @@ namespace DetourNavigator private: mutable std::mutex mMutex; std::condition_variable mHasJob; - std::deque mJobs; + SpatialJobQueue mReading; + std::deque mWriting; + TilePosition mPlayerTile; bool mShouldStop = false; - std::size_t mWritingJobs = 0; - std::size_t mReadingJobs = 0; }; class AsyncNavMeshUpdater; @@ -112,7 +165,7 @@ namespace DetourNavigator void enqueueJob(JobIt job); - void updateJobs(TilePosition playerTile, int maxTiles) { mQueue.update(playerTile, maxTiles); } + void update(TilePosition playerTile) { mQueue.update(playerTile); } void stop(); @@ -169,7 +222,7 @@ namespace DetourNavigator std::condition_variable mDone; std::condition_variable mProcessed; std::list mJobs; - std::deque mWaiting; + JobQueue mWaiting; std::set> mPushed; Misc::ScopeGuarded mPlayerTile; NavMeshTilesCache mNavMeshTilesCache; diff --git a/components/detournavigator/changetype.hpp b/components/detournavigator/changetype.hpp index e6d0bce0a9..63a43e88fb 100644 --- a/components/detournavigator/changetype.hpp +++ b/components/detournavigator/changetype.hpp @@ -6,15 +6,9 @@ namespace DetourNavigator enum class ChangeType { remove = 0, - mixed = 1, - add = 2, - update = 3, + add = 1, + update = 2, }; - - inline ChangeType addChangeType(const ChangeType current, const ChangeType add) - { - return current == add ? current : ChangeType::mixed; - } } #endif diff --git a/components/detournavigator/debug.cpp b/components/detournavigator/debug.cpp index 4dcd5e9857..5ce1464bdd 100644 --- a/components/detournavigator/debug.cpp +++ b/components/detournavigator/debug.cpp @@ -184,8 +184,6 @@ namespace DetourNavigator { case ChangeType::remove: return stream << "ChangeType::remove"; - case ChangeType::mixed: - return stream << "ChangeType::mixed"; case ChangeType::add: return stream << "ChangeType::add"; case ChangeType::update: diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index ea20f8bc34..3b62866ed7 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -188,7 +188,7 @@ namespace DetourNavigator if (shouldAdd && !presentInNavMesh) tilesToPost.emplace(tile, locked->isEmptyTile(tile) ? ChangeType::update : ChangeType::add); else if (!shouldAdd && presentInNavMesh) - tilesToPost.emplace(tile, ChangeType::mixed); + tilesToPost.emplace(tile, ChangeType::remove); }); locked->forEachTilePosition([&](const TilePosition& tile) { if (!shouldAddTile(tile, playerTile, maxTiles)) diff --git a/components/detournavigator/stats.cpp b/components/detournavigator/stats.cpp index 1d7dcac553..da56f91a38 100644 --- a/components/detournavigator/stats.cpp +++ b/components/detournavigator/stats.cpp @@ -9,16 +9,18 @@ namespace DetourNavigator void reportStats(const AsyncNavMeshUpdaterStats& stats, unsigned int frameNumber, osg::Stats& out) { out.setAttribute(frameNumber, "NavMesh Jobs", static_cast(stats.mJobs)); - out.setAttribute(frameNumber, "NavMesh Waiting", static_cast(stats.mWaiting)); + out.setAttribute(frameNumber, "NavMesh Removing", static_cast(stats.mWaiting.mRemoving)); + out.setAttribute(frameNumber, "NavMesh Updating", static_cast(stats.mWaiting.mUpdating)); + out.setAttribute(frameNumber, "NavMesh Delayed", static_cast(stats.mWaiting.mDelayed)); out.setAttribute(frameNumber, "NavMesh Pushed", static_cast(stats.mPushed)); out.setAttribute(frameNumber, "NavMesh Processing", static_cast(stats.mProcessing)); if (stats.mDb.has_value()) { - out.setAttribute( - frameNumber, "NavMesh DbJobs Write", static_cast(stats.mDb->mJobs.mWritingJobs)); out.setAttribute( frameNumber, "NavMesh DbJobs Read", static_cast(stats.mDb->mJobs.mReadingJobs)); + out.setAttribute( + frameNumber, "NavMesh DbJobs Write", static_cast(stats.mDb->mJobs.mWritingJobs)); out.setAttribute(frameNumber, "NavMesh DbCache Get", static_cast(stats.mDb->mGetTileCount)); out.setAttribute(frameNumber, "NavMesh DbCache Hit", static_cast(stats.mDbGetTileHits)); diff --git a/components/detournavigator/stats.hpp b/components/detournavigator/stats.hpp index c644f1db87..0b62b9e669 100644 --- a/components/detournavigator/stats.hpp +++ b/components/detournavigator/stats.hpp @@ -11,10 +11,17 @@ namespace osg namespace DetourNavigator { + struct JobQueueStats + { + std::size_t mRemoving = 0; + std::size_t mUpdating = 0; + std::size_t mDelayed = 0; + }; + struct DbJobQueueStats { - std::size_t mWritingJobs = 0; std::size_t mReadingJobs = 0; + std::size_t mWritingJobs = 0; }; struct DbWorkerStats @@ -35,7 +42,7 @@ namespace DetourNavigator struct AsyncNavMeshUpdaterStats { std::size_t mJobs = 0; - std::size_t mWaiting = 0; + JobQueueStats mWaiting; std::size_t mPushed = 0; std::size_t mProcessing = 0; std::size_t mDbGetTileHits = 0; diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index 0bab808300..3e3927bf65 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -10,7 +10,6 @@ #include -#include #include #include @@ -416,7 +415,7 @@ namespace DetourNavigator if (tile == mChangedTiles.end()) mChangedTiles.emplace(tilePosition, changeType); else - tile->second = addChangeType(tile->second, changeType); + tile->second = changeType == ChangeType::remove ? changeType : tile->second; } std::map TileCachedRecastMeshManager::takeChangedTiles(const UpdateGuard* guard) diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 65b009deff..9bb90635d1 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -49,6 +49,8 @@ namespace Resource std::vector generateAllStatNames() { + constexpr std::size_t itemsPerPage = 24; + constexpr std::string_view firstPage[] = { "FrameNumber", "", @@ -76,6 +78,8 @@ namespace Resource "", }; + static_assert(std::size(firstPage) == itemsPerPage); + constexpr std::string_view caches[] = { "Node", "Shape", @@ -100,7 +104,9 @@ namespace Resource constexpr std::string_view navMesh[] = { "NavMesh Jobs", - "NavMesh Waiting", + "NavMesh Removing", + "NavMesh Updating", + "NavMesh Delayed", "NavMesh Pushed", "NavMesh Processing", "NavMesh DbJobs Write", @@ -129,7 +135,8 @@ namespace Resource for (std::string_view name : cellPreloader) statNames.emplace_back(name); - statNames.emplace_back(); + while (statNames.size() % itemsPerPage != 0) + statNames.emplace_back(); for (std::string_view name : navMesh) statNames.emplace_back(name); From 910c88325a69f4166a468ae30b643641f9410ed0 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 7 Apr 2024 15:22:02 +0200 Subject: [PATCH 385/451] Add a setting to wait for all navmesh jobs on exit --- apps/openmw/mwworld/worldimp.cpp | 6 ++++++ components/settings/categories/navigator.hpp | 1 + docs/source/reference/modding/settings/navigator.rst | 10 ++++++++++ files/settings-default.cfg | 3 +++ 4 files changed, 20 insertions(+) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ec58f779d0..74335a1534 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -591,6 +591,12 @@ namespace MWWorld // Must be cleared before mRendering is destroyed if (mProjectileManager) mProjectileManager->clear(); + + if (Settings::navigator().mWaitForAllJobsOnExit) + { + Log(Debug::Verbose) << "Waiting for all navmesh jobs to be done..."; + mNavigator->wait(DetourNavigator::WaitConditionType::allJobsDone, nullptr); + } } void World::setRandomSeed(uint32_t seed) diff --git a/components/settings/categories/navigator.hpp b/components/settings/categories/navigator.hpp index d6d7adcd56..c65dd3392e 100644 --- a/components/settings/categories/navigator.hpp +++ b/components/settings/categories/navigator.hpp @@ -63,6 +63,7 @@ namespace Settings SettingValue mEnableNavMeshDiskCache{ mIndex, "Navigator", "enable nav mesh disk cache" }; SettingValue mWriteToNavmeshdb{ mIndex, "Navigator", "write to navmeshdb" }; SettingValue mMaxNavmeshdbFileSize{ mIndex, "Navigator", "max navmeshdb file size" }; + SettingValue mWaitForAllJobsOnExit{ mIndex, "Navigator", "wait for all jobs on exit" }; }; } diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index 55b9e19b19..6fafdcdfd2 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -245,6 +245,16 @@ Absent pieces usually mean a bug in recast mesh tiles building. Allows to do in-game debug. Potentially decreases performance. +wait for all jobs on exit +------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Wait until all queued async navmesh jobs are processed before exiting the engine. +Useful when a benchmark generates jobs to write into navmeshdb faster than they are processed. + Expert settings *************** diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 2f236bf55a..10c25bb430 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -999,6 +999,9 @@ write to navmeshdb = true # Approximate maximum file size of navigation mesh cache stored on disk in bytes (value > 0) max navmeshdb file size = 2147483648 +# Wait until all queued async navmesh jobs are processed before exiting the engine (true, false) +wait for all jobs on exit = false + [Shadows] # Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true. From ea029b06eabc98d620ac63b9f6c60730708e601a Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 13 Apr 2024 18:51:45 +0100 Subject: [PATCH 386/451] Remove unused define --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 93da5feec4..9e6e788c05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -710,7 +710,6 @@ if (WIN32) if (USE_DEBUG_CONSOLE AND BUILD_OPENMW) set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE") set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE") - set_target_properties(openmw PROPERTIES COMPILE_DEFINITIONS $<$:_CONSOLE>) elseif (BUILD_OPENMW) # Turn off debug console, debug output will be written to visual studio output instead set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS") From 901a17ab81a4814d51dc7b5531cfef3cbdcabec3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 13 Apr 2024 19:01:34 +0100 Subject: [PATCH 387/451] Make comments stop lying --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e6e788c05..3360ac2e2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,7 +234,7 @@ else() endif(APPLE) if (WIN32) - option(USE_DEBUG_CONSOLE "whether a debug console should be enabled for debug builds, if false debug output is redirected to Visual Studio output" ON) + option(USE_DEBUG_CONSOLE "Whether a console should be displayed if OpenMW isn't launched from the command line. Does not affect the Release configuration." ON) endif() if(MSVC) @@ -711,7 +711,7 @@ if (WIN32) set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE") set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE") elseif (BUILD_OPENMW) - # Turn off debug console, debug output will be written to visual studio output instead + # Turn off implicit console, you won't see stdout unless launching OpenMW from a command line shell or look at openmw.log set_target_properties(openmw PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:WINDOWS") set_target_properties(openmw PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:WINDOWS") endif() From d6452e7d29d7b354b411a1cff8c200dd5653fb87 Mon Sep 17 00:00:00 2001 From: Alexander Olofsson Date: Sat, 13 Apr 2024 23:16:28 +0000 Subject: [PATCH 388/451] Update appdata XML, add branding colors --- files/openmw.appdata.xml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/files/openmw.appdata.xml b/files/openmw.appdata.xml index aacdc1dd96..764082df00 100644 --- a/files/openmw.appdata.xml +++ b/files/openmw.appdata.xml @@ -21,6 +21,12 @@ Copyright 2020 Bret Curtis

org.openmw.launcher.desktop + + + #dcccb8 + #574526 + + https://wiki.openmw.org/images/Openmw_0.11.1_launcher_1.png @@ -28,15 +34,15 @@ Copyright 2020 Bret Curtis https://wiki.openmw.org/images/0.40_Screenshot-Balmora_3.png - The Mournhold's plaza on OpenMW + Balmora at morning on OpenMW https://wiki.openmw.org/images/Screenshot_mournhold_plaza_0.35.png - Vivec seen from Ebonheart on OpenMW + The Mournhold's plaza on OpenMW https://wiki.openmw.org/images/Screenshot_Vivec_seen_from_Ebonheart_0.35.png - Balmora at morning on OpenMW + Vivec seen from Ebonheart on OpenMW From 1ad9e5f9e897cce9d67058c40e32a9e4b10f2044 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 14 Apr 2024 08:17:10 +0400 Subject: [PATCH 389/451] Rework editor icons --- CI/before_script.msvc.sh | 22 +- CI/install_debian_deps.sh | 2 +- CMakeLists.txt | 10 +- apps/opencs/CMakeLists.txt | 4 +- apps/opencs/main.cpp | 2 +- apps/opencs/model/world/tablemimedata.cpp | 2 +- apps/opencs/model/world/universalid.cpp | 215 ++++++------ apps/opencs/view/doc/view.cpp | 43 ++- apps/opencs/view/filter/editwidget.cpp | 2 +- apps/opencs/view/render/mask.hpp | 9 +- .../view/render/pagedworldspacewidget.cpp | 8 +- apps/opencs/view/render/pathgridmode.cpp | 4 +- .../view/render/unpagedworldspacewidget.cpp | 1 - apps/opencs/view/render/worldspacewidget.hpp | 3 +- apps/opencs/view/world/recordbuttonbar.cpp | 2 +- apps/opencs/view/world/table.cpp | 4 +- .../model/world/testuniversalid.cpp | 29 +- files/opencs/activator.png | Bin 562 -> 0 bytes files/opencs/activator.svg | 126 +++++++ files/opencs/apparatus.png | Bin 364 -> 0 bytes files/opencs/apparatus.svg | 58 ++++ files/opencs/armor.png | Bin 473 -> 0 bytes files/opencs/armor.svg | 58 ++++ files/opencs/attribute.png | Bin 326 -> 0 bytes files/opencs/attribute.svg | 46 +++ files/opencs/birthsign.png | Bin 444 -> 0 bytes files/opencs/birthsign.svg | 94 +++++ files/opencs/body-part.png | Bin 418 -> 0 bytes files/opencs/body-part.svg | 58 ++++ files/opencs/book.png | Bin 323 -> 0 bytes files/opencs/book.svg | 166 +++++++++ files/opencs/brush-circle.png | Bin 1540 -> 0 bytes files/opencs/brush-circle.svg | 51 +++ files/opencs/brush-custom.png | Bin 2475 -> 0 bytes files/opencs/brush-custom.svg | 56 +++ files/opencs/brush-point.png | Bin 787 -> 0 bytes files/opencs/brush-point.svg | 65 ++++ files/opencs/brush-square.png | Bin 761 -> 0 bytes files/opencs/brush-square.svg | 52 +++ files/opencs/camera-first-person.png | Bin 750 -> 0 bytes files/opencs/camera-first-person.svg | 63 ++++ files/opencs/camera-free.png | Bin 783 -> 0 bytes files/opencs/camera-free.svg | 76 ++++ files/opencs/camera-orbit.png | Bin 1096 -> 0 bytes files/opencs/camera-orbit.svg | 62 ++++ files/opencs/cell.png | Bin 1212 -> 0 bytes files/opencs/cell.svg | 83 +++++ files/opencs/class.png | Bin 588 -> 0 bytes files/opencs/class.svg | 186 ++++++++++ files/opencs/clothing.png | Bin 369 -> 0 bytes files/opencs/clothing.svg | 101 ++++++ files/opencs/container.png | Bin 421 -> 0 bytes files/opencs/container.svg | 93 +++++ files/opencs/creature.png | Bin 463 -> 0 bytes files/opencs/creature.svg | 189 ++++++++++ files/opencs/debug-profile.png | Bin 431 -> 0 bytes files/opencs/debug-profile.svg | 115 +++++++ files/opencs/dialogue-greeting.png | Bin 386 -> 0 bytes files/opencs/dialogue-info.png | Bin 306 -> 0 bytes files/opencs/dialogue-info.svg | 89 +++++ files/opencs/dialogue-journal.png | Bin 288 -> 0 bytes files/opencs/dialogue-persuasion.png | Bin 362 -> 0 bytes files/opencs/dialogue-regular.png | Bin 221 -> 0 bytes files/opencs/dialogue-topic-infos.png | Bin 306 -> 0 bytes files/opencs/dialogue-topics.png | Bin 282 -> 0 bytes files/opencs/dialogue-topics.svg | 89 +++++ files/opencs/dialogue-voice.png | Bin 294 -> 0 bytes files/opencs/door.png | Bin 381 -> 0 bytes files/opencs/door.svg | 77 +++++ files/opencs/editing-instance.png | Bin 986 -> 0 bytes files/opencs/editing-instance.svg | 51 +++ files/opencs/editing-pathgrid.png | Bin 2297 -> 0 bytes files/opencs/editing-pathgrid.svg | 83 +++++ files/opencs/editing-terrain-movement.png | Bin 1745 -> 0 bytes files/opencs/editing-terrain-movement.svg | 58 ++++ files/opencs/editing-terrain-shape.png | Bin 1399 -> 0 bytes files/opencs/editing-terrain-shape.svg | 43 +++ files/opencs/editing-terrain-texture.png | Bin 2100 -> 0 bytes files/opencs/editing-terrain-texture.svg | 82 +++++ files/opencs/editing-terrain-vertex-paint.png | Bin 2545 -> 0 bytes files/opencs/editing-terrain-vertex-paint.svg | 111 ++++++ files/opencs/enchantment.png | Bin 422 -> 0 bytes files/opencs/enchantment.svg | 54 +++ files/opencs/error-log.png | Bin 518 -> 0 bytes files/opencs/error-log.svg | 134 ++++++++ files/opencs/faction.png | Bin 389 -> 0 bytes files/opencs/faction.svg | 73 ++++ files/opencs/filter.png | Bin 299 -> 0 bytes files/opencs/filter.svg | 61 ++++ files/opencs/global-variable.png | Bin 282 -> 0 bytes files/opencs/global-variable.svg | 86 +++++ files/opencs/gmst.png | Bin 338 -> 0 bytes files/opencs/gmst.svg | 114 ++++++ files/opencs/info.png | Bin 1234 -> 0 bytes files/opencs/info.svg | 85 +++++ files/opencs/ingredient.png | Bin 490 -> 0 bytes files/opencs/ingredient.svg | 97 ++++++ files/opencs/instance.png | Bin 618 -> 0 bytes files/opencs/instance.svg | 126 +++++++ files/opencs/journal-topic-infos.png | Bin 300 -> 0 bytes files/opencs/journal-topic-infos.svg | 97 ++++++ files/opencs/journal-topics.png | Bin 273 -> 0 bytes files/opencs/journal-topics.svg | 73 ++++ files/opencs/land-heightmap.png | Bin 193 -> 0 bytes files/opencs/land-heightmap.svg | 86 +++++ files/opencs/land-texture.png | Bin 288 -> 0 bytes files/opencs/land-texture.svg | 97 ++++++ files/opencs/levelled-creature.png | Bin 521 -> 0 bytes files/opencs/levelled-creature.svg | 198 +++++++++++ files/opencs/levelled-item.png | Bin 516 -> 0 bytes files/opencs/levelled-item.svg | 137 ++++++++ files/opencs/light.png | Bin 384 -> 0 bytes files/opencs/light.svg | 105 ++++++ files/opencs/lighting-lamp.png | Bin 953 -> 0 bytes files/opencs/lighting-lamp.svg | 40 +++ files/opencs/lighting-moon.png | Bin 1590 -> 0 bytes files/opencs/lighting-moon.svg | 50 +++ files/opencs/lighting-sun.png | Bin 1804 -> 0 bytes files/opencs/lighting-sun.svg | 143 ++++++++ files/opencs/list-added.png | Bin 166 -> 0 bytes files/opencs/list-added.svg | 101 ++++++ files/opencs/list-base.png | Bin 184 -> 0 bytes files/opencs/list-base.svg | 120 +++++++ files/opencs/list-modified.png | Bin 457 -> 0 bytes files/opencs/list-modified.svg | 101 ++++++ files/opencs/list-removed.png | Bin 134 -> 0 bytes files/opencs/list-removed.svg | 108 ++++++ files/opencs/lockpick.png | Bin 317 -> 0 bytes files/opencs/lockpick.svg | 108 ++++++ files/opencs/magic-effect.png | Bin 356 -> 0 bytes files/opencs/magic-effect.svg | 194 +++++++++++ files/opencs/menu-close.png | Bin 438 -> 0 bytes files/opencs/menu-close.svg | 202 +++++++++++ files/opencs/menu-exit.png | Bin 257 -> 0 bytes files/opencs/menu-exit.svg | 101 ++++++ files/opencs/menu-merge.png | Bin 219 -> 0 bytes files/opencs/menu-merge.svg | 126 +++++++ files/opencs/menu-new-addon.png | Bin 282 -> 0 bytes files/opencs/menu-new-addon.svg | 124 +++++++ files/opencs/menu-new-game.png | Bin 230 -> 0 bytes files/opencs/menu-new-game.svg | 111 ++++++ files/opencs/menu-new-window.png | Bin 213 -> 0 bytes files/opencs/menu-new-window.svg | 123 +++++++ files/opencs/menu-open.png | Bin 241 -> 0 bytes files/opencs/menu-open.svg | 111 ++++++ files/opencs/menu-preferences.png | Bin 385 -> 0 bytes files/opencs/menu-preferences.svg | 232 +++++++++++++ files/opencs/menu-redo.png | Bin 323 -> 0 bytes files/opencs/menu-redo.svg | 122 +++++++ files/opencs/menu-reload.png | Bin 447 -> 0 bytes files/opencs/menu-reload.svg | 111 ++++++ files/opencs/menu-save.png | Bin 302 -> 0 bytes files/opencs/menu-save.svg | 70 ++++ files/opencs/menu-search.png | Bin 408 -> 0 bytes files/opencs/menu-search.svg | 157 +++++++++ files/opencs/menu-status-bar.png | Bin 188 -> 0 bytes files/opencs/menu-status-bar.svg | 131 +++++++ files/opencs/menu-undo.png | Bin 323 -> 0 bytes files/opencs/menu-undo.svg | 130 +++++++ files/opencs/menu-verify.png | Bin 487 -> 0 bytes files/opencs/menu-verify.svg | 109 ++++++ files/opencs/metadata.png | Bin 389 -> 0 bytes files/opencs/metadata.svg | 173 ++++++++++ files/opencs/miscellaneous.png | Bin 466 -> 0 bytes files/opencs/miscellaneous.svg | 118 +++++++ files/opencs/multitype.png | Bin 1708 -> 0 bytes files/opencs/multitype.svg | 117 +++++++ files/opencs/npc.png | Bin 465 -> 0 bytes files/opencs/npc.svg | 316 +++++++++++++++++ files/opencs/object.png | Bin 447 -> 0 bytes files/opencs/object.svg | 118 +++++++ files/opencs/pathgrid.png | Bin 407 -> 0 bytes files/opencs/pathgrid.svg | 175 ++++++++++ files/opencs/potion.png | Bin 442 -> 0 bytes files/opencs/potion.svg | 146 ++++++++ files/opencs/probe.png | Bin 304 -> 0 bytes files/opencs/probe.svg | 134 ++++++++ files/opencs/qt.png | Bin 405 -> 0 bytes files/opencs/qt.svg | 180 ++++++++++ files/opencs/race.png | Bin 451 -> 0 bytes files/opencs/race.svg | 160 +++++++++ files/opencs/random.png | Bin 1892 -> 0 bytes files/opencs/record-add.png | Bin 238 -> 0 bytes files/opencs/record-add.svg | 134 ++++++++ files/opencs/record-clone.png | Bin 275 -> 0 bytes files/opencs/record-clone.svg | 158 +++++++++ files/opencs/record-delete.png | Bin 151 -> 0 bytes files/opencs/record-delete.svg | 142 ++++++++ files/opencs/record-down.png | Bin 150 -> 0 bytes files/opencs/record-down.svg | 144 ++++++++ files/opencs/record-edit.png | Bin 220 -> 0 bytes files/opencs/record-edit.svg | 132 +++++++ files/opencs/record-next.png | Bin 248 -> 0 bytes files/opencs/record-next.svg | 143 ++++++++ files/opencs/record-preview.png | Bin 334 -> 0 bytes files/opencs/record-preview.svg | 178 ++++++++++ files/opencs/record-previous.png | Bin 265 -> 0 bytes files/opencs/record-previous.svg | 143 ++++++++ files/opencs/record-revert.png | Bin 323 -> 0 bytes files/opencs/record-revert.svg | 154 +++++++++ files/opencs/record-touch.png | Bin 289 -> 0 bytes files/opencs/record-touch.svg | 143 ++++++++ files/opencs/record-up.png | Bin 150 -> 0 bytes files/opencs/record-up.svg | 144 ++++++++ files/opencs/region-map.png | Bin 428 -> 0 bytes files/opencs/region-map.svg | 155 +++++++++ files/opencs/region.png | Bin 605 -> 0 bytes files/opencs/region.svg | 143 ++++++++ files/opencs/repair.png | Bin 367 -> 0 bytes files/opencs/repair.svg | 119 +++++++ files/opencs/resources-icon.png | Bin 194 -> 0 bytes files/opencs/resources-icon.svg | 178 ++++++++++ files/opencs/resources-mesh.png | Bin 586 -> 0 bytes files/opencs/resources-mesh.svg | 153 +++++++++ files/opencs/resources-music.png | Bin 354 -> 0 bytes files/opencs/resources-music.svg | 216 ++++++++++++ files/opencs/resources-sound.png | Bin 222 -> 0 bytes files/opencs/resources-sound.svg | 142 ++++++++ files/opencs/resources-texture.png | Bin 288 -> 0 bytes files/opencs/resources-texture.svg | 154 +++++++++ files/opencs/resources-video.png | Bin 277 -> 0 bytes files/opencs/resources-video.svg | 155 +++++++++ files/opencs/resources.qrc | 324 ++++++++---------- files/opencs/run-game.png | Bin 642 -> 0 bytes files/opencs/run-game.svg | 135 ++++++++ files/opencs/run-log.png | Bin 294 -> 0 bytes files/opencs/run-log.svg | 83 +++++ files/opencs/run-openmw.png | Bin 388 -> 0 bytes files/opencs/run-openmw.svg | 133 +++++++ files/opencs/scalable/editor-icons.svg | 32 +- files/opencs/scene-exterior-arrows.png | Bin 511 -> 0 bytes files/opencs/scene-exterior-arrows.svg | 145 ++++++++ files/opencs/scene-exterior-borders.png | Bin 429 -> 0 bytes files/opencs/scene-exterior-borders.svg | 135 ++++++++ files/opencs/scene-exterior-markers.png | Bin 497 -> 0 bytes files/opencs/scene-exterior-markers.svg | 148 ++++++++ files/opencs/scene-exterior-status-0.png | Bin 1932 -> 0 bytes files/opencs/scene-exterior-status-0.svg | 167 +++++++++ files/opencs/scene-exterior-status-1.png | Bin 1912 -> 0 bytes files/opencs/scene-exterior-status-1.svg | 168 +++++++++ files/opencs/scene-exterior-status-2.png | Bin 1969 -> 0 bytes files/opencs/scene-exterior-status-2.svg | 168 +++++++++ files/opencs/scene-exterior-status-3.png | Bin 1946 -> 0 bytes files/opencs/scene-exterior-status-3.svg | 168 +++++++++ files/opencs/scene-exterior-status-4.png | Bin 1943 -> 0 bytes files/opencs/scene-exterior-status-4.svg | 168 +++++++++ files/opencs/scene-exterior-status-5.png | Bin 1924 -> 0 bytes files/opencs/scene-exterior-status-5.svg | 168 +++++++++ files/opencs/scene-exterior-status-6.png | Bin 1980 -> 0 bytes files/opencs/scene-exterior-status-6.svg | 168 +++++++++ files/opencs/scene-exterior-status-7.png | Bin 1957 -> 0 bytes files/opencs/scene-exterior-status-7.svg | 168 +++++++++ files/opencs/scene-view-fog.png | Bin 298 -> 0 bytes files/opencs/scene-view-instance.png | Bin 1774 -> 0 bytes files/opencs/scene-view-instance.svg | 155 +++++++++ files/opencs/scene-view-pathgrid.png | Bin 569 -> 0 bytes files/opencs/scene-view-pathgrid.svg | 136 ++++++++ files/opencs/scene-view-status-0.png | Bin 3547 -> 0 bytes files/opencs/scene-view-status-0.svg | 158 +++++++++ files/opencs/scene-view-status-1.png | Bin 1774 -> 0 bytes files/opencs/scene-view-status-1.svg | 154 +++++++++ files/opencs/scene-view-status-10.png | Bin 777 -> 0 bytes files/opencs/scene-view-status-10.svg | 148 ++++++++ files/opencs/scene-view-status-11.png | Bin 2424 -> 0 bytes files/opencs/scene-view-status-11.svg | 171 +++++++++ files/opencs/scene-view-status-12.png | Bin 923 -> 0 bytes files/opencs/scene-view-status-12.svg | 148 ++++++++ files/opencs/scene-view-status-13.png | Bin 2287 -> 0 bytes files/opencs/scene-view-status-13.svg | 171 +++++++++ files/opencs/scene-view-status-14.png | Bin 1302 -> 0 bytes files/opencs/scene-view-status-14.svg | 152 ++++++++ files/opencs/scene-view-status-15.png | Bin 2701 -> 0 bytes files/opencs/scene-view-status-15.svg | 175 ++++++++++ files/opencs/scene-view-status-16.png | Bin 2580 -> 0 bytes files/opencs/scene-view-status-17.png | Bin 3077 -> 0 bytes files/opencs/scene-view-status-18.png | Bin 2947 -> 0 bytes files/opencs/scene-view-status-19.png | Bin 3470 -> 0 bytes files/opencs/scene-view-status-2.png | Bin 569 -> 0 bytes files/opencs/scene-view-status-2.svg | 135 ++++++++ files/opencs/scene-view-status-20.png | Bin 2491 -> 0 bytes files/opencs/scene-view-status-21.png | Bin 3009 -> 0 bytes files/opencs/scene-view-status-22.png | Bin 2872 -> 0 bytes files/opencs/scene-view-status-23.png | Bin 3394 -> 0 bytes files/opencs/scene-view-status-24.png | Bin 2731 -> 0 bytes files/opencs/scene-view-status-25.png | Bin 3200 -> 0 bytes files/opencs/scene-view-status-26.png | Bin 3125 -> 0 bytes files/opencs/scene-view-status-27.png | Bin 3627 -> 0 bytes files/opencs/scene-view-status-28.png | Bin 2695 -> 0 bytes files/opencs/scene-view-status-29.png | Bin 3125 -> 0 bytes files/opencs/scene-view-status-3.png | Bin 2169 -> 0 bytes files/opencs/scene-view-status-3.svg | 158 +++++++++ files/opencs/scene-view-status-30.png | Bin 3096 -> 0 bytes files/opencs/scene-view-status-31.png | Bin 3547 -> 0 bytes files/opencs/scene-view-status-4.png | Bin 753 -> 0 bytes files/opencs/scene-view-status-4.svg | 135 ++++++++ files/opencs/scene-view-status-5.png | Bin 2097 -> 0 bytes files/opencs/scene-view-status-5.svg | 158 +++++++++ files/opencs/scene-view-status-6.png | Bin 1104 -> 0 bytes files/opencs/scene-view-status-6.svg | 139 ++++++++ files/opencs/scene-view-status-7.png | Bin 2462 -> 0 bytes files/opencs/scene-view-status-7.svg | 162 +++++++++ files/opencs/scene-view-status-8.png | Bin 298 -> 0 bytes files/opencs/scene-view-status-8.svg | 144 ++++++++ files/opencs/scene-view-status-9.png | Bin 1990 -> 0 bytes files/opencs/scene-view-status-9.svg | 167 +++++++++ files/opencs/scene-view-terrain.png | Bin 2580 -> 0 bytes files/opencs/scene-view-terrain.svg | 145 ++++++++ files/opencs/scene-view-water.png | Bin 753 -> 0 bytes files/opencs/scene-view-water.svg | 136 ++++++++ files/opencs/scene.png | Bin 401 -> 0 bytes files/opencs/scene.svg | 178 ++++++++++ files/opencs/script.png | Bin 256 -> 0 bytes files/opencs/script.svg | 143 ++++++++ files/opencs/selection-mode-cube-corner.png | Bin 1422 -> 0 bytes files/opencs/selection-mode-cube-corner.svg | 62 ++++ files/opencs/selection-mode-cube-sphere.png | Bin 1428 -> 0 bytes files/opencs/selection-mode-cube.png | Bin 1391 -> 0 bytes files/opencs/selection-mode-cube.svg | 58 ++++ files/opencs/selection-mode-sphere.svg | 72 ++++ files/opencs/skill.png | Bin 318 -> 0 bytes files/opencs/skill.svg | 143 ++++++++ files/opencs/sound-generator.png | Bin 429 -> 0 bytes files/opencs/sound-generator.svg | 142 ++++++++ files/opencs/sound.png | Bin 222 -> 0 bytes files/opencs/sound.svg | 165 +++++++++ files/opencs/spell.png | Bin 319 -> 0 bytes files/opencs/spell.svg | 264 ++++++++++++++ files/opencs/start-script.png | Bin 359 -> 0 bytes files/opencs/start-script.svg | 155 +++++++++ files/opencs/static.png | Bin 208 -> 0 bytes files/opencs/static.svg | 154 +++++++++ files/opencs/stop-openmw.png | Bin 252 -> 0 bytes files/opencs/stop-openmw.svg | 147 ++++++++ files/opencs/transform-move.png | Bin 879 -> 0 bytes files/opencs/transform-move.svg | 60 ++++ files/opencs/transform-rotate.png | Bin 1391 -> 0 bytes files/opencs/transform-rotate.svg | 62 ++++ files/opencs/transform-scale.png | Bin 436 -> 0 bytes files/opencs/transform-scale.svg | 60 ++++ files/opencs/weapon.png | Bin 394 -> 0 bytes files/opencs/weapon.svg | 142 ++++++++ 341 files changed, 19217 insertions(+), 373 deletions(-) delete mode 100644 files/opencs/activator.png create mode 100644 files/opencs/activator.svg delete mode 100644 files/opencs/apparatus.png create mode 100644 files/opencs/apparatus.svg delete mode 100644 files/opencs/armor.png create mode 100644 files/opencs/armor.svg delete mode 100644 files/opencs/attribute.png create mode 100644 files/opencs/attribute.svg delete mode 100644 files/opencs/birthsign.png create mode 100644 files/opencs/birthsign.svg delete mode 100644 files/opencs/body-part.png create mode 100644 files/opencs/body-part.svg delete mode 100644 files/opencs/book.png create mode 100644 files/opencs/book.svg delete mode 100644 files/opencs/brush-circle.png create mode 100644 files/opencs/brush-circle.svg delete mode 100644 files/opencs/brush-custom.png create mode 100644 files/opencs/brush-custom.svg delete mode 100644 files/opencs/brush-point.png create mode 100644 files/opencs/brush-point.svg delete mode 100644 files/opencs/brush-square.png create mode 100644 files/opencs/brush-square.svg delete mode 100644 files/opencs/camera-first-person.png create mode 100644 files/opencs/camera-first-person.svg delete mode 100644 files/opencs/camera-free.png create mode 100644 files/opencs/camera-free.svg delete mode 100644 files/opencs/camera-orbit.png create mode 100644 files/opencs/camera-orbit.svg delete mode 100644 files/opencs/cell.png create mode 100644 files/opencs/cell.svg delete mode 100644 files/opencs/class.png create mode 100644 files/opencs/class.svg delete mode 100644 files/opencs/clothing.png create mode 100644 files/opencs/clothing.svg delete mode 100644 files/opencs/container.png create mode 100644 files/opencs/container.svg delete mode 100644 files/opencs/creature.png create mode 100644 files/opencs/creature.svg delete mode 100644 files/opencs/debug-profile.png create mode 100644 files/opencs/debug-profile.svg delete mode 100644 files/opencs/dialogue-greeting.png delete mode 100644 files/opencs/dialogue-info.png create mode 100644 files/opencs/dialogue-info.svg delete mode 100644 files/opencs/dialogue-journal.png delete mode 100644 files/opencs/dialogue-persuasion.png delete mode 100644 files/opencs/dialogue-regular.png delete mode 100644 files/opencs/dialogue-topic-infos.png delete mode 100644 files/opencs/dialogue-topics.png create mode 100644 files/opencs/dialogue-topics.svg delete mode 100644 files/opencs/dialogue-voice.png delete mode 100644 files/opencs/door.png create mode 100644 files/opencs/door.svg delete mode 100644 files/opencs/editing-instance.png create mode 100644 files/opencs/editing-instance.svg delete mode 100644 files/opencs/editing-pathgrid.png create mode 100644 files/opencs/editing-pathgrid.svg delete mode 100644 files/opencs/editing-terrain-movement.png create mode 100644 files/opencs/editing-terrain-movement.svg delete mode 100644 files/opencs/editing-terrain-shape.png create mode 100644 files/opencs/editing-terrain-shape.svg delete mode 100644 files/opencs/editing-terrain-texture.png create mode 100644 files/opencs/editing-terrain-texture.svg delete mode 100644 files/opencs/editing-terrain-vertex-paint.png create mode 100644 files/opencs/editing-terrain-vertex-paint.svg delete mode 100644 files/opencs/enchantment.png create mode 100644 files/opencs/enchantment.svg delete mode 100644 files/opencs/error-log.png create mode 100644 files/opencs/error-log.svg delete mode 100644 files/opencs/faction.png create mode 100644 files/opencs/faction.svg delete mode 100644 files/opencs/filter.png create mode 100644 files/opencs/filter.svg delete mode 100644 files/opencs/global-variable.png create mode 100644 files/opencs/global-variable.svg delete mode 100644 files/opencs/gmst.png create mode 100644 files/opencs/gmst.svg delete mode 100644 files/opencs/info.png create mode 100644 files/opencs/info.svg delete mode 100644 files/opencs/ingredient.png create mode 100644 files/opencs/ingredient.svg delete mode 100644 files/opencs/instance.png create mode 100644 files/opencs/instance.svg delete mode 100644 files/opencs/journal-topic-infos.png create mode 100644 files/opencs/journal-topic-infos.svg delete mode 100644 files/opencs/journal-topics.png create mode 100644 files/opencs/journal-topics.svg delete mode 100644 files/opencs/land-heightmap.png create mode 100644 files/opencs/land-heightmap.svg delete mode 100644 files/opencs/land-texture.png create mode 100644 files/opencs/land-texture.svg delete mode 100644 files/opencs/levelled-creature.png create mode 100644 files/opencs/levelled-creature.svg delete mode 100644 files/opencs/levelled-item.png create mode 100644 files/opencs/levelled-item.svg delete mode 100644 files/opencs/light.png create mode 100644 files/opencs/light.svg delete mode 100644 files/opencs/lighting-lamp.png create mode 100644 files/opencs/lighting-lamp.svg delete mode 100644 files/opencs/lighting-moon.png create mode 100644 files/opencs/lighting-moon.svg delete mode 100644 files/opencs/lighting-sun.png create mode 100644 files/opencs/lighting-sun.svg delete mode 100644 files/opencs/list-added.png create mode 100644 files/opencs/list-added.svg delete mode 100644 files/opencs/list-base.png create mode 100644 files/opencs/list-base.svg delete mode 100644 files/opencs/list-modified.png create mode 100644 files/opencs/list-modified.svg delete mode 100644 files/opencs/list-removed.png create mode 100644 files/opencs/list-removed.svg delete mode 100644 files/opencs/lockpick.png create mode 100644 files/opencs/lockpick.svg delete mode 100644 files/opencs/magic-effect.png create mode 100644 files/opencs/magic-effect.svg delete mode 100644 files/opencs/menu-close.png create mode 100644 files/opencs/menu-close.svg delete mode 100644 files/opencs/menu-exit.png create mode 100644 files/opencs/menu-exit.svg delete mode 100644 files/opencs/menu-merge.png create mode 100644 files/opencs/menu-merge.svg delete mode 100644 files/opencs/menu-new-addon.png create mode 100644 files/opencs/menu-new-addon.svg delete mode 100644 files/opencs/menu-new-game.png create mode 100644 files/opencs/menu-new-game.svg delete mode 100644 files/opencs/menu-new-window.png create mode 100644 files/opencs/menu-new-window.svg delete mode 100644 files/opencs/menu-open.png create mode 100644 files/opencs/menu-open.svg delete mode 100644 files/opencs/menu-preferences.png create mode 100644 files/opencs/menu-preferences.svg delete mode 100644 files/opencs/menu-redo.png create mode 100644 files/opencs/menu-redo.svg delete mode 100644 files/opencs/menu-reload.png create mode 100644 files/opencs/menu-reload.svg delete mode 100644 files/opencs/menu-save.png create mode 100644 files/opencs/menu-save.svg delete mode 100644 files/opencs/menu-search.png create mode 100644 files/opencs/menu-search.svg delete mode 100644 files/opencs/menu-status-bar.png create mode 100644 files/opencs/menu-status-bar.svg delete mode 100644 files/opencs/menu-undo.png create mode 100644 files/opencs/menu-undo.svg delete mode 100644 files/opencs/menu-verify.png create mode 100644 files/opencs/menu-verify.svg delete mode 100644 files/opencs/metadata.png create mode 100644 files/opencs/metadata.svg delete mode 100644 files/opencs/miscellaneous.png create mode 100644 files/opencs/miscellaneous.svg delete mode 100644 files/opencs/multitype.png create mode 100644 files/opencs/multitype.svg delete mode 100644 files/opencs/npc.png create mode 100644 files/opencs/npc.svg delete mode 100644 files/opencs/object.png create mode 100644 files/opencs/object.svg delete mode 100644 files/opencs/pathgrid.png create mode 100644 files/opencs/pathgrid.svg delete mode 100644 files/opencs/potion.png create mode 100644 files/opencs/potion.svg delete mode 100644 files/opencs/probe.png create mode 100644 files/opencs/probe.svg delete mode 100644 files/opencs/qt.png create mode 100644 files/opencs/qt.svg delete mode 100644 files/opencs/race.png create mode 100644 files/opencs/race.svg delete mode 100644 files/opencs/random.png delete mode 100644 files/opencs/record-add.png create mode 100644 files/opencs/record-add.svg delete mode 100644 files/opencs/record-clone.png create mode 100644 files/opencs/record-clone.svg delete mode 100644 files/opencs/record-delete.png create mode 100644 files/opencs/record-delete.svg delete mode 100644 files/opencs/record-down.png create mode 100644 files/opencs/record-down.svg delete mode 100644 files/opencs/record-edit.png create mode 100644 files/opencs/record-edit.svg delete mode 100644 files/opencs/record-next.png create mode 100644 files/opencs/record-next.svg delete mode 100644 files/opencs/record-preview.png create mode 100644 files/opencs/record-preview.svg delete mode 100644 files/opencs/record-previous.png create mode 100644 files/opencs/record-previous.svg delete mode 100644 files/opencs/record-revert.png create mode 100644 files/opencs/record-revert.svg delete mode 100644 files/opencs/record-touch.png create mode 100644 files/opencs/record-touch.svg delete mode 100644 files/opencs/record-up.png create mode 100644 files/opencs/record-up.svg delete mode 100644 files/opencs/region-map.png create mode 100644 files/opencs/region-map.svg delete mode 100644 files/opencs/region.png create mode 100644 files/opencs/region.svg delete mode 100644 files/opencs/repair.png create mode 100644 files/opencs/repair.svg delete mode 100644 files/opencs/resources-icon.png create mode 100644 files/opencs/resources-icon.svg delete mode 100644 files/opencs/resources-mesh.png create mode 100644 files/opencs/resources-mesh.svg delete mode 100644 files/opencs/resources-music.png create mode 100644 files/opencs/resources-music.svg delete mode 100644 files/opencs/resources-sound.png create mode 100644 files/opencs/resources-sound.svg delete mode 100644 files/opencs/resources-texture.png create mode 100644 files/opencs/resources-texture.svg delete mode 100644 files/opencs/resources-video.png create mode 100644 files/opencs/resources-video.svg delete mode 100644 files/opencs/run-game.png create mode 100644 files/opencs/run-game.svg delete mode 100644 files/opencs/run-log.png create mode 100644 files/opencs/run-log.svg delete mode 100644 files/opencs/run-openmw.png create mode 100644 files/opencs/run-openmw.svg delete mode 100644 files/opencs/scene-exterior-arrows.png create mode 100644 files/opencs/scene-exterior-arrows.svg delete mode 100644 files/opencs/scene-exterior-borders.png create mode 100644 files/opencs/scene-exterior-borders.svg delete mode 100644 files/opencs/scene-exterior-markers.png create mode 100644 files/opencs/scene-exterior-markers.svg delete mode 100644 files/opencs/scene-exterior-status-0.png create mode 100644 files/opencs/scene-exterior-status-0.svg delete mode 100644 files/opencs/scene-exterior-status-1.png create mode 100644 files/opencs/scene-exterior-status-1.svg delete mode 100644 files/opencs/scene-exterior-status-2.png create mode 100644 files/opencs/scene-exterior-status-2.svg delete mode 100644 files/opencs/scene-exterior-status-3.png create mode 100644 files/opencs/scene-exterior-status-3.svg delete mode 100644 files/opencs/scene-exterior-status-4.png create mode 100644 files/opencs/scene-exterior-status-4.svg delete mode 100644 files/opencs/scene-exterior-status-5.png create mode 100644 files/opencs/scene-exterior-status-5.svg delete mode 100644 files/opencs/scene-exterior-status-6.png create mode 100644 files/opencs/scene-exterior-status-6.svg delete mode 100644 files/opencs/scene-exterior-status-7.png create mode 100644 files/opencs/scene-exterior-status-7.svg delete mode 100644 files/opencs/scene-view-fog.png delete mode 100644 files/opencs/scene-view-instance.png create mode 100644 files/opencs/scene-view-instance.svg delete mode 100644 files/opencs/scene-view-pathgrid.png create mode 100644 files/opencs/scene-view-pathgrid.svg delete mode 100644 files/opencs/scene-view-status-0.png create mode 100644 files/opencs/scene-view-status-0.svg delete mode 100644 files/opencs/scene-view-status-1.png create mode 100644 files/opencs/scene-view-status-1.svg delete mode 100644 files/opencs/scene-view-status-10.png create mode 100644 files/opencs/scene-view-status-10.svg delete mode 100644 files/opencs/scene-view-status-11.png create mode 100644 files/opencs/scene-view-status-11.svg delete mode 100644 files/opencs/scene-view-status-12.png create mode 100644 files/opencs/scene-view-status-12.svg delete mode 100644 files/opencs/scene-view-status-13.png create mode 100644 files/opencs/scene-view-status-13.svg delete mode 100644 files/opencs/scene-view-status-14.png create mode 100644 files/opencs/scene-view-status-14.svg delete mode 100644 files/opencs/scene-view-status-15.png create mode 100644 files/opencs/scene-view-status-15.svg delete mode 100644 files/opencs/scene-view-status-16.png delete mode 100644 files/opencs/scene-view-status-17.png delete mode 100644 files/opencs/scene-view-status-18.png delete mode 100644 files/opencs/scene-view-status-19.png delete mode 100644 files/opencs/scene-view-status-2.png create mode 100644 files/opencs/scene-view-status-2.svg delete mode 100644 files/opencs/scene-view-status-20.png delete mode 100644 files/opencs/scene-view-status-21.png delete mode 100644 files/opencs/scene-view-status-22.png delete mode 100644 files/opencs/scene-view-status-23.png delete mode 100644 files/opencs/scene-view-status-24.png delete mode 100644 files/opencs/scene-view-status-25.png delete mode 100644 files/opencs/scene-view-status-26.png delete mode 100644 files/opencs/scene-view-status-27.png delete mode 100644 files/opencs/scene-view-status-28.png delete mode 100644 files/opencs/scene-view-status-29.png delete mode 100644 files/opencs/scene-view-status-3.png create mode 100644 files/opencs/scene-view-status-3.svg delete mode 100644 files/opencs/scene-view-status-30.png delete mode 100644 files/opencs/scene-view-status-31.png delete mode 100644 files/opencs/scene-view-status-4.png create mode 100644 files/opencs/scene-view-status-4.svg delete mode 100644 files/opencs/scene-view-status-5.png create mode 100644 files/opencs/scene-view-status-5.svg delete mode 100644 files/opencs/scene-view-status-6.png create mode 100644 files/opencs/scene-view-status-6.svg delete mode 100644 files/opencs/scene-view-status-7.png create mode 100644 files/opencs/scene-view-status-7.svg delete mode 100644 files/opencs/scene-view-status-8.png create mode 100644 files/opencs/scene-view-status-8.svg delete mode 100644 files/opencs/scene-view-status-9.png create mode 100644 files/opencs/scene-view-status-9.svg delete mode 100644 files/opencs/scene-view-terrain.png create mode 100644 files/opencs/scene-view-terrain.svg delete mode 100644 files/opencs/scene-view-water.png create mode 100644 files/opencs/scene-view-water.svg delete mode 100644 files/opencs/scene.png create mode 100644 files/opencs/scene.svg delete mode 100644 files/opencs/script.png create mode 100644 files/opencs/script.svg delete mode 100644 files/opencs/selection-mode-cube-corner.png create mode 100644 files/opencs/selection-mode-cube-corner.svg delete mode 100644 files/opencs/selection-mode-cube-sphere.png delete mode 100644 files/opencs/selection-mode-cube.png create mode 100644 files/opencs/selection-mode-cube.svg create mode 100644 files/opencs/selection-mode-sphere.svg delete mode 100644 files/opencs/skill.png create mode 100644 files/opencs/skill.svg delete mode 100644 files/opencs/sound-generator.png create mode 100644 files/opencs/sound-generator.svg delete mode 100644 files/opencs/sound.png create mode 100644 files/opencs/sound.svg delete mode 100644 files/opencs/spell.png create mode 100644 files/opencs/spell.svg delete mode 100644 files/opencs/start-script.png create mode 100644 files/opencs/start-script.svg delete mode 100644 files/opencs/static.png create mode 100644 files/opencs/static.svg delete mode 100644 files/opencs/stop-openmw.png create mode 100644 files/opencs/stop-openmw.svg delete mode 100644 files/opencs/transform-move.png create mode 100644 files/opencs/transform-move.svg delete mode 100644 files/opencs/transform-rotate.png create mode 100644 files/opencs/transform-rotate.svg delete mode 100644 files/opencs/transform-scale.png create mode 100644 files/opencs/transform-scale.svg delete mode 100644 files/opencs/weapon.png create mode 100644 files/opencs/weapon.svg diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 269cc44707..3919507bd1 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -347,6 +347,16 @@ add_qt_style_dlls() { QT_STYLES[$CONFIG]="${QT_STYLES[$CONFIG]} $@" } +declare -A QT_IMAGEFORMATS +QT_IMAGEFORMATS["Release"]="" +QT_IMAGEFORMATS["Debug"]="" +QT_IMAGEFORMATS["RelWithDebInfo"]="" +add_qt_image_dlls() { + local CONFIG=$1 + shift + QT_IMAGEFORMATS[$CONFIG]="${QT_IMAGEFORMATS[$CONFIG]} $@" +} + if [ -z $PLATFORM ]; then PLATFORM="$(uname -m)" fi @@ -913,12 +923,13 @@ printf "Qt ${QT_VER}... " DLLSUFFIX="" fi if [ "${QT_VER:0:1}" -eq "6" ]; then - add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets}${DLLSUFFIX}.dll + add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets,Svg}${DLLSUFFIX}.dll else - add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll + add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,Widgets,Svg}${DLLSUFFIX}.dll fi add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll" add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" + add_qt_image_dlls $CONFIGURATION "$(pwd)/plugins/imageformats/qsvg${DLLSUFFIX}.dll" done echo Done. } @@ -1112,6 +1123,13 @@ fi echo " $(basename $DLL)" cp "$DLL" "${DLL_PREFIX}styles" done + + echo "- Qt Image Format DLLs..." + mkdir -p ${DLL_PREFIX}imageformats + for DLL in ${QT_IMAGEFORMATS[$CONFIGURATION]}; do + echo " $(basename $DLL)" + cp "$DLL" "${DLL_PREFIX}imageformats" + done echo done #fi diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index b7784cf3f0..d29f16f55f 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -36,7 +36,7 @@ declare -rA GROUPED_DEPS=( libsdl2-dev libqt5opengl5-dev qttools5-dev qttools5-dev-tools libopenal-dev libunshield-dev libtinyxml-dev libbullet-dev liblz4-dev libpng-dev libjpeg-dev libluajit-5.1-dev librecast-dev libsqlite3-dev ca-certificates libicu-dev - libyaml-cpp-dev + libyaml-cpp-dev libqt5svg5 libqt5svg5-dev " # These dependencies can alternatively be built and linked statically. diff --git a/CMakeLists.txt b/CMakeLists.txt index 93da5feec4..5de8934968 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -249,9 +249,9 @@ find_package(LZ4 REQUIRED) if (USE_QT) find_package(QT REQUIRED COMPONENTS Core NAMES Qt6 Qt5) if (QT_VERSION_MAJOR VERSION_EQUAL 5) - find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL LinguistTools REQUIRED) + find_package(Qt5 5.15 COMPONENTS Core Widgets Network OpenGL LinguistTools Svg REQUIRED) else() - find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets LinguistTools REQUIRED) + find_package(Qt6 COMPONENTS Core Widgets Network OpenGL OpenGLWidgets LinguistTools Svg REQUIRED) endif() message(STATUS "Using Qt${QT_VERSION}") endif() @@ -834,6 +834,12 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE) configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY) configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY) + + get_property(QT_QSVG_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgPlugin PROPERTY LOCATION_RELEASE) + get_filename_component(QT_QSVG_PLUGIN_DIR "${QT_QSVG_PLUGIN_PATH}" DIRECTORY) + get_filename_component(QT_QSVG_PLUGIN_GROUP "${QT_QSVG_PLUGIN_DIR}" NAME) + get_filename_component(QT_QSVG_PLUGIN_NAME "${QT_QSVG_PLUGIN_PATH}" NAME) + configure_file("${QT_QSVG_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QSVG_PLUGIN_GROUP}/${QT_QSVG_PLUGIN_NAME}" COPYONLY) endif () install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "." COMPONENT Runtime) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index c2f249171a..3353a4586f 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -250,9 +250,9 @@ target_link_libraries(openmw-cs-lib ) if (QT_VERSION_MAJOR VERSION_EQUAL 6) - target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::OpenGLWidgets) + target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::OpenGLWidgets Qt::Svg) else() - target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL) + target_link_libraries(openmw-cs-lib Qt::Widgets Qt::Core Qt::Network Qt::OpenGL Qt::Svg) endif() if (WIN32) diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index e7f980dc0d..62fb29e600 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -81,7 +81,7 @@ int runApplication(int argc, char* argv[]) Application application(argc, argv); - application.setWindowIcon(QIcon(":./openmw-cs.png")); + application.setWindowIcon(QIcon(":openmw-cs")); CS::Editor editor(argc, argv); #ifdef __linux__ diff --git a/apps/opencs/model/world/tablemimedata.cpp b/apps/opencs/model/world/tablemimedata.cpp index a966a53eee..a40698bc27 100644 --- a/apps/opencs/model/world/tablemimedata.cpp +++ b/apps/opencs/model/world/tablemimedata.cpp @@ -58,7 +58,7 @@ std::string CSMWorld::TableMimeData::getIcon() const if (tmpIcon != id.getIcon()) { - return ":/multitype.png"; // icon stolen from gnome TODO: get new icon + return ":multitype"; } tmpIcon = id.getIcon(); diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 9daf87e20a..0ebccd6253 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -23,158 +23,137 @@ namespace constexpr TypeData sNoArg[] = { { CSMWorld::UniversalId::Class_None, CSMWorld::UniversalId::Type_None, "-", ":placeholder" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Globals, "Global Variables", - ":./global-variable.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings", ":./gmst.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills", ":./skill.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Classes, "Classes", ":./class.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Factions, "Factions", ":./faction.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Races, "Races", ":./race.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Sounds, "Sounds", ":./sound.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Scripts, "Scripts", ":./script.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", ":./region.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", - ":./birthsign.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", ":./spell.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", - ":./dialogue-topics.png" }, + ":global-variable" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Gmsts, "Game Settings", ":gmst" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Skills, "Skills", ":skill" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Classes, "Classes", ":class" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Factions, "Factions", ":faction" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Races, "Races", ":race" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Sounds, "Sounds", ":sound" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Scripts, "Scripts", ":script" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Regions, "Regions", ":region" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Birthsigns, "Birthsigns", ":birthsign" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Spells, "Spells", ":spell" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Topics, "Topics", ":dialogue-topics" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Journals, "Journals", - ":./journal-topics.png" }, + ":journal-topics" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_TopicInfos, "Topic Infos", - ":./dialogue-topic-infos.png" }, + ":dialogue-info" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_JournalInfos, "Journal Infos", - ":./journal-topic-infos.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", ":./cell.png" }, + ":journal-topic-infos" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Cells, "Cells", ":cell" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Enchantments, "Enchantments", - ":./enchantment.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_BodyParts, "Body Parts", - ":./body-part.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, "Objects", - ":./object.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References, "Instances", - ":./instance.png" }, - { CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap, "Region Map", - ":./region-map.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", ":./filter.png" }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Meshes, "Meshes", - ":./resources-mesh" }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Icons, "Icons", ":./resources-icon" }, + ":enchantment" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_BodyParts, "Body Parts", ":body-part" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Referenceables, "Objects", ":object" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_References, "Instances", ":instance" }, + { CSMWorld::UniversalId::Class_NonRecord, CSMWorld::UniversalId::Type_RegionMap, "Region Map", ":region-map" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Filters, "Filters", ":filter" }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Meshes, "Meshes", ":resources-mesh" }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Icons, "Icons", ":resources-icon" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Musics, "Music Files", - ":./resources-music" }, + ":resources-music" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_SoundsRes, "Sound Files", ":resources-sound" }, { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Textures, "Textures", - ":./resources-texture" }, - { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", - ":./resources-video" }, + ":resources-texture" }, + { CSMWorld::UniversalId::Class_ResourceList, CSMWorld::UniversalId::Type_Videos, "Videos", ":resources-video" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_DebugProfiles, "Debug Profiles", - ":./debug-profile.png" }, + ":debug-profile" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SelectionGroup, "Selection Groups", "" }, - { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", ":./run-log.png" }, + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", ":run-log" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SoundGens, "Sound Generators", - ":./sound-generator.png" }, + ":sound-generator" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MagicEffects, "Magic Effects", - ":./magic-effect.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Lands", - ":./land-heightmap.png" }, + ":magic-effect" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Lands, "Lands", ":land-heightmap" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_LandTextures, "Land Textures", - ":./land-texture.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", - ":./pathgrid.png" }, + ":land-texture" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_Pathgrids, "Pathgrids", ":pathgrid" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_StartScripts, "Start Scripts", - ":./start-script.png" }, - { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Metadata", - ":./metadata.png" }, + ":start-script" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_MetaDatas, "Metadata", ":metadata" }, }; constexpr TypeData sIdArg[] = { { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Global, "Global Variable", - ":./global-variable.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", ":./gmst.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill", ":./skill.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Class, "Class", ":./class.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Faction, "Faction", ":./faction.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Race, "Race", ":./race.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Sound, "Sound", ":./sound.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Script, "Script", ":./script.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", ":./region.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", ":./birthsign.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":./spell.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", ":./dialogue-topics.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", - ":./journal-topics.png" }, + ":global-variable" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Gmst, "Game Setting", ":gmst" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Skill, "Skill", ":skill" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Class, "Class", ":class" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Faction, "Faction", ":faction" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Race, "Race", ":race" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Sound, "Sound", ":sound" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Script, "Script", ":script" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Region, "Region", ":region" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Birthsign, "Birthsign", ":birthsign" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Spell, "Spell", ":spell" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Topic, "Topic", ":dialogue-topics" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Journal, "Journal", ":journal-topics" }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_TopicInfo, "TopicInfo", - ":./dialogue-topic-infos.png" }, + ":dialogue-info" }, { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_JournalInfo, "JournalInfo", - ":./journal-topic-infos.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":./cell.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":./cell.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Object", ":./object.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", - ":./activator.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Potion, "Potion", ":./potion.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Apparatus, "Apparatus", - ":./apparatus.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Armor, "Armor", ":./armor.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Book, "Book", ":./book.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Clothing, "Clothing", ":./clothing.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Container, "Container", - ":./container.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Creature, "Creature", ":./creature.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Door, "Door", ":./door.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Ingredient, "Ingredient", - ":./ingredient.png" }, + ":journal-topic-infos" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell, "Cell", ":cell" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Cell_Missing, "Cell", ":cell" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Referenceable, "Object", ":object" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Activator, "Activator", ":activator" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Potion, "Potion", ":potion" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Apparatus, "Apparatus", ":apparatus" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Armor, "Armor", ":armor" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Book, "Book", ":book" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Clothing, "Clothing", ":clothing" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Container, "Container", ":container" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Creature, "Creature", ":creature" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Door, "Door", ":door" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Ingredient, "Ingredient", ":ingredient" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_CreatureLevelledList, - "Creature Levelled List", ":./levelled-creature.png" }, + "Creature Levelled List", ":levelled-creature" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_ItemLevelledList, "Item Levelled List", - ":./levelled-item.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":./light.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Lockpick, "Lockpick", ":./lockpick.png" }, + ":levelled-item" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Light, "Light", ":light" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Lockpick, "Lockpick", ":lockpick" }, { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Miscellaneous, "Miscellaneous", - ":./miscellaneous.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Npc, "NPC", ":./npc.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Probe, "Probe", ":./probe.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Repair, "Repair", ":./repair.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":./static.png" }, - { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":./weapon.png" }, - { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Instance", - ":./instance.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":./filter.png" }, - { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", ":./scene.png" }, - { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", - ":./record-preview.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", - ":./enchantment.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_BodyPart, "Body Part", ":./body-part.png" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", ":./resources-mesh" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", ":./resources-icon" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Music, "Music", ":./resources-music" }, + ":miscellaneous" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Npc, "NPC", ":npc" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Probe, "Probe", ":probe" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Repair, "Repair", ":repair" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Static, "Static", ":static" }, + { CSMWorld::UniversalId::Class_RefRecord, CSMWorld::UniversalId::Type_Weapon, "Weapon", ":weapon" }, + { CSMWorld::UniversalId::Class_SubRecord, CSMWorld::UniversalId::Type_Reference, "Instance", ":instance" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Filter, "Filter", ":filter" }, + { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Scene, "Scene", ":scene" }, + { CSMWorld::UniversalId::Class_Collection, CSMWorld::UniversalId::Type_Preview, "Preview", ":edit-preview" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Enchantment, "Enchantment", ":enchantment" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_BodyPart, "Body Part", ":body-part" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Mesh, "Mesh", ":resources-mesh" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Icon, "Icon", ":resources-icon" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Music, "Music", ":resources-music" }, { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_SoundRes, "Sound File", - ":./resources-sound" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", - ":./resources-texture" }, - { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", ":./resources-video" }, + ":resources-sound" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Texture, "Texture", ":resources-texture" }, + { CSMWorld::UniversalId::Class_Resource, CSMWorld::UniversalId::Type_Video, "Video", ":resources-video" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_DebugProfile, "Debug Profile", - ":./debug-profile.png" }, + ":debug-profile" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_SoundGen, "Sound Generator", - ":./sound-generator.png" }, + ":sound-generator" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MagicEffect, "Magic Effect", - ":./magic-effect.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", ":./land-heightmap.png" }, + ":magic-effect" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Land, "Land", ":land-heightmap" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_LandTexture, "Land Texture", - ":./land-texture.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", ":./pathgrid.png" }, + ":land-texture" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_Pathgrid, "Pathgrid", ":pathgrid" }, { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_StartScript, "Start Script", - ":./start-script.png" }, - { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Metadata", ":./metadata.png" }, + ":start-script" }, + { CSMWorld::UniversalId::Class_Record, CSMWorld::UniversalId::Type_MetaData, "Metadata", ":metadata" }, }; constexpr TypeData sIndexArg[] = { { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_VerificationResults, - "Verification Results", ":./menu-verify.png" }, + "Verification Results", ":menu-verify" }, { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_LoadErrorLog, "Load Error Log", - ":./error-log.png" }, - { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_Search, "Global Search", - ":./menu-search.png" }, + ":error-log" }, + { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_Search, "Global Search", ":menu-search" }, }; struct WriteToStream diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index e1bf7e6ac6..f5cdb1b8fc 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -71,30 +71,30 @@ void CSVDoc::View::setupFileMenu() { QMenu* file = menuBar()->addMenu(tr("File")); - QAction* newGame = createMenuEntry("New Game", ":./menu-new-game.png", file, "document-file-newgame"); + QAction* newGame = createMenuEntry("New Game", ":menu-new-game", file, "document-file-newgame"); connect(newGame, &QAction::triggered, this, &View::newGameRequest); - QAction* newAddon = createMenuEntry("New Addon", ":./menu-new-addon.png", file, "document-file-newaddon"); + QAction* newAddon = createMenuEntry("New Addon", ":menu-new-addon", file, "document-file-newaddon"); connect(newAddon, &QAction::triggered, this, &View::newAddonRequest); - QAction* open = createMenuEntry("Open", ":./menu-open.png", file, "document-file-open"); + QAction* open = createMenuEntry("Open", ":menu-open", file, "document-file-open"); connect(open, &QAction::triggered, this, &View::loadDocumentRequest); - QAction* save = createMenuEntry("Save", ":./menu-save.png", file, "document-file-save"); + QAction* save = createMenuEntry("Save", ":menu-save", file, "document-file-save"); connect(save, &QAction::triggered, this, &View::save); mSave = save; file->addSeparator(); - QAction* verify = createMenuEntry("Verify", ":./menu-verify.png", file, "document-file-verify"); + QAction* verify = createMenuEntry("Verify", ":menu-verify", file, "document-file-verify"); connect(verify, &QAction::triggered, this, &View::verify); mVerify = verify; - QAction* merge = createMenuEntry("Merge", ":./menu-merge.png", file, "document-file-merge"); + QAction* merge = createMenuEntry("Merge", ":menu-merge", file, "document-file-merge"); connect(merge, &QAction::triggered, this, &View::merge); mMerge = merge; - QAction* loadErrors = createMenuEntry("Error Log", ":./error-log.png", file, "document-file-errorlog"); + QAction* loadErrors = createMenuEntry("Error Log", ":error-log", file, "document-file-errorlog"); connect(loadErrors, &QAction::triggered, this, &View::loadErrorLog); QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata"); @@ -102,10 +102,10 @@ void CSVDoc::View::setupFileMenu() file->addSeparator(); - QAction* close = createMenuEntry("Close", ":./menu-close.png", file, "document-file-close"); + QAction* close = createMenuEntry("Close", ":menu-close", file, "document-file-close"); connect(close, &QAction::triggered, this, &View::close); - QAction* exit = createMenuEntry("Exit", ":./menu-exit.png", file, "document-file-exit"); + QAction* exit = createMenuEntry("Exit", ":menu-exit", file, "document-file-exit"); connect(exit, &QAction::triggered, this, &View::exit); connect(this, &View::exitApplicationRequest, &mViewManager, &ViewManager::exitApplication); @@ -140,17 +140,16 @@ void CSVDoc::View::setupEditMenu() mUndo = mDocument->getUndoStack().createUndoAction(this, tr("Undo")); setupShortcut("document-edit-undo", mUndo); connect(mUndo, &QAction::changed, this, &View::undoActionChanged); - mUndo->setIcon(QIcon(QString::fromStdString(":./menu-undo.png"))); + mUndo->setIcon(QIcon(QString::fromStdString(":menu-undo"))); edit->addAction(mUndo); mRedo = mDocument->getUndoStack().createRedoAction(this, tr("Redo")); connect(mRedo, &QAction::changed, this, &View::redoActionChanged); setupShortcut("document-edit-redo", mRedo); - mRedo->setIcon(QIcon(QString::fromStdString(":./menu-redo.png"))); + mRedo->setIcon(QIcon(QString::fromStdString(":menu-redo"))); edit->addAction(mRedo); - QAction* userSettings - = createMenuEntry("Preferences", ":./menu-preferences.png", edit, "document-edit-preferences"); + QAction* userSettings = createMenuEntry("Preferences", ":menu-preferences", edit, "document-edit-preferences"); connect(userSettings, &QAction::triggered, this, &View::editSettingsRequest); QAction* search = createMenuEntry(CSMWorld::UniversalId::Type_Search, edit, "document-edit-search"); @@ -161,10 +160,10 @@ void CSVDoc::View::setupViewMenu() { QMenu* view = menuBar()->addMenu(tr("View")); - QAction* newWindow = createMenuEntry("New View", ":./menu-new-window.png", view, "document-view-newview"); + QAction* newWindow = createMenuEntry("New View", ":menu-new-window", view, "document-view-newview"); connect(newWindow, &QAction::triggered, this, &View::newView); - mShowStatusBar = createMenuEntry("Toggle Status Bar", ":./menu-status-bar.png", view, "document-view-statusbar"); + mShowStatusBar = createMenuEntry("Toggle Status Bar", ":menu-status-bar", view, "document-view-statusbar"); connect(mShowStatusBar, &QAction::toggled, this, &View::toggleShowStatusBar); mShowStatusBar->setCheckable(true); mShowStatusBar->setChecked(CSMPrefs::get()["Windows"]["show-statusbar"].isTrue()); @@ -289,7 +288,7 @@ void CSVDoc::View::setupAssetsMenu() { QMenu* assets = menuBar()->addMenu(tr("Assets")); - QAction* reload = createMenuEntry("Reload", ":./menu-reload.png", assets, "document-assets-reload"); + QAction* reload = createMenuEntry("Reload", ":menu-reload", assets, "document-assets-reload"); connect(reload, &QAction::triggered, &mDocument->getData(), &CSMWorld::Data::assetsChanged); assets->addSeparator(); @@ -341,9 +340,9 @@ void CSVDoc::View::setupDebugMenu() QAction* runDebug = debug->addMenu(mGlobalDebugProfileMenu); runDebug->setText(tr("Run OpenMW")); setupShortcut("document-debug-run", runDebug); - runDebug->setIcon(QIcon(QString::fromStdString(":./run-openmw.png"))); + runDebug->setIcon(QIcon(QString::fromStdString(":run-openmw"))); - QAction* stopDebug = createMenuEntry("Stop OpenMW", ":./stop-openmw.png", debug, "document-debug-shutdown"); + QAction* stopDebug = createMenuEntry("Stop OpenMW", ":stop-openmw", debug, "document-debug-shutdown"); connect(stopDebug, &QAction::triggered, this, &View::stop); mStopDebug = stopDebug; @@ -355,16 +354,16 @@ void CSVDoc::View::setupHelpMenu() { QMenu* help = menuBar()->addMenu(tr("Help")); - QAction* helpInfo = createMenuEntry("Help", ":/info.png", help, "document-help-help"); + QAction* helpInfo = createMenuEntry("Help", ":info", help, "document-help-help"); connect(helpInfo, &QAction::triggered, this, &View::openHelp); - QAction* tutorial = createMenuEntry("Tutorial", ":/info.png", help, "document-help-tutorial"); + QAction* tutorial = createMenuEntry("Tutorial", ":info", help, "document-help-tutorial"); connect(tutorial, &QAction::triggered, this, &View::tutorial); - QAction* about = createMenuEntry("About OpenMW-CS", ":./info.png", help, "document-help-about"); + QAction* about = createMenuEntry("About OpenMW-CS", ":info", help, "document-help-about"); connect(about, &QAction::triggered, this, &View::infoAbout); - QAction* aboutQt = createMenuEntry("About Qt", ":./qt.png", help, "document-help-qt"); + QAction* aboutQt = createMenuEntry("About Qt", ":qt", help, "document-help-qt"); connect(aboutQt, &QAction::triggered, this, &View::infoAboutQt); } diff --git a/apps/opencs/view/filter/editwidget.cpp b/apps/opencs/view/filter/editwidget.cpp index 50735bf703..0d7ab679b2 100644 --- a/apps/opencs/view/filter/editwidget.cpp +++ b/apps/opencs/view/filter/editwidget.cpp @@ -44,7 +44,7 @@ CSVFilter::EditWidget::EditWidget(CSMWorld::Data& data, QWidget* parent) mHelpAction = new QAction(tr("Help"), this); connect(mHelpAction, &QAction::triggered, this, &EditWidget::openHelp); - mHelpAction->setIcon(QIcon(":/info.png")); + mHelpAction->setIcon(QIcon(":info")); addAction(mHelpAction); auto* openHelpShortcut = new CSMPrefs::Shortcut("help", this); openHelpShortcut->associateAction(mHelpAction); diff --git a/apps/opencs/view/render/mask.hpp b/apps/opencs/view/render/mask.hpp index 7f767e19ac..1c84d886d3 100644 --- a/apps/opencs/view/render/mask.hpp +++ b/apps/opencs/view/render/mask.hpp @@ -12,11 +12,10 @@ namespace CSVRender { // elements that are part of the actual scene Mask_Hidden = 0x0, - Mask_Reference = 0x2, - Mask_Pathgrid = 0x4, - Mask_Water = 0x8, - Mask_Fog = 0x10, - Mask_Terrain = 0x20, + Mask_Reference = 0x1, + Mask_Pathgrid = 0x2, + Mask_Water = 0x4, + Mask_Terrain = 0x8, // used within models Mask_ParticleSystem = 0x100, diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 214618a627..58523ab595 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -160,7 +160,6 @@ void CSVRender::PagedWorldspaceWidget::addVisibilitySelectorButtons(CSVWidget::S { WorldspaceWidget::addVisibilitySelectorButtons(tool); tool->addButton(Button_Terrain, Mask_Terrain, "Terrain"); - tool->addButton(Button_Fog, Mask_Fog, "Fog", "", true); } void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons(CSVWidget::SceneToolMode* tool) @@ -170,9 +169,10 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons(CSVWidget::Sce /// \todo replace EditMode with suitable subclasses tool->addButton(new TerrainShapeMode(this, mRootNode, tool), "terrain-shape"); tool->addButton(new TerrainTextureMode(this, mRootNode, tool), "terrain-texture"); - tool->addButton( - new EditMode(this, QIcon(":placeholder"), Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex"); - tool->addButton(new EditMode(this, QIcon(":placeholder"), Mask_Reference, "Terrain movement"), "terrain-move"); + const QIcon vertexIcon = QIcon(":scenetoolbar/editing-terrain-vertex-paint"); + const QIcon movementIcon = QIcon(":scenetoolbar/editing-terrain-movement"); + tool->addButton(new EditMode(this, vertexIcon, Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex"); + tool->addButton(new EditMode(this, movementIcon, Mask_Reference, "Terrain movement"), "terrain-move"); } void CSVRender::PagedWorldspaceWidget::handleInteractionPress(const WorldspaceHitResult& hit, InteractionType type) diff --git a/apps/opencs/view/render/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp index 5c45e2b31f..8550195e8c 100644 --- a/apps/opencs/view/render/pathgridmode.cpp +++ b/apps/opencs/view/render/pathgridmode.cpp @@ -36,8 +36,8 @@ class QWidget; namespace CSVRender { PathgridMode::PathgridMode(WorldspaceWidget* worldspaceWidget, QWidget* parent) - : EditMode(worldspaceWidget, QIcon(":placeholder"), Mask_Pathgrid | Mask_Terrain | Mask_Reference, getTooltip(), - parent) + : EditMode(worldspaceWidget, QIcon(":scenetoolbar/editing-pathgrid"), + Mask_Pathgrid | Mask_Terrain | Mask_Reference, getTooltip(), parent) , mDragMode(DragMode_None) , mFromNode(0) , mSelectionMode(nullptr) diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 899918c3b9..fee43b5de5 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -347,7 +347,6 @@ void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons(CSVWidget: { WorldspaceWidget::addVisibilitySelectorButtons(tool); tool->addButton(Button_Terrain, Mask_Terrain, "Terrain", "", true); - tool->addButton(Button_Fog, Mask_Fog, "Fog"); } std::string CSVRender::UnpagedWorldspaceWidget::getStartupInstruction() diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 505d985ffa..06470d2883 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -222,8 +222,7 @@ namespace CSVRender Button_Reference = 0x1, Button_Pathgrid = 0x2, Button_Water = 0x4, - Button_Fog = 0x8, - Button_Terrain = 0x10 + Button_Terrain = 0x8 }; virtual void addVisibilitySelectorButtons(CSVWidget::SceneToolToggle2* tool); diff --git a/apps/opencs/view/world/recordbuttonbar.cpp b/apps/opencs/view/world/recordbuttonbar.cpp index 67270bd1ed..1333a4a7da 100644 --- a/apps/opencs/view/world/recordbuttonbar.cpp +++ b/apps/opencs/view/world/recordbuttonbar.cpp @@ -92,7 +92,7 @@ CSVWorld::RecordButtonBar::RecordButtonBar(const CSMWorld::UniversalId& id, CSMW if (mTable.getFeatures() & CSMWorld::IdTable::Feature_View) { QToolButton* viewButton = new QToolButton(this); - viewButton->setIcon(QIcon(":/cell.png")); + viewButton->setIcon(QIcon(":cell")); viewButton->setToolTip("Open a scene view of the cell this record is located in"); buttonsLayout->addWidget(viewButton); connect(viewButton, &QToolButton::clicked, this, &RecordButtonBar::viewRecord); diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 4212e952e8..b7be2b90c8 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -385,7 +385,7 @@ CSVWorld::Table::Table(const CSMWorld::UniversalId& id, bool createAndDelete, bo mViewAction = new QAction(tr("View"), this); connect(mViewAction, &QAction::triggered, this, &Table::viewRecord); - mViewAction->setIcon(QIcon(":/cell.png")); + mViewAction->setIcon(QIcon(":cell")); addAction(mViewAction); CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this); viewShortcut->associateAction(mViewAction); @@ -417,7 +417,7 @@ CSVWorld::Table::Table(const CSMWorld::UniversalId& id, bool createAndDelete, bo mHelpAction = new QAction(tr("Help"), this); connect(mHelpAction, &QAction::triggered, this, &Table::openHelp); - mHelpAction->setIcon(QIcon(":/info.png")); + mHelpAction->setIcon(QIcon(":info")); addAction(mHelpAction); CSMPrefs::Shortcut* openHelpShortcut = new CSMPrefs::Shortcut("help", this); openHelpShortcut->associateAction(mHelpAction); diff --git a/apps/opencs_tests/model/world/testuniversalid.cpp b/apps/opencs_tests/model/world/testuniversalid.cpp index 54538a591d..871a5218d4 100644 --- a/apps/opencs_tests/model/world/testuniversalid.cpp +++ b/apps/opencs_tests/model/world/testuniversalid.cpp @@ -149,39 +149,36 @@ namespace CSMWorld Params{ UniversalId(UniversalId::Type_None), UniversalId::Type_None, UniversalId::Class_None, UniversalId::ArgumentType_None, "-", "-", ":placeholder" }, Params{ UniversalId(UniversalId::Type_RegionMap), UniversalId::Type_RegionMap, UniversalId::Class_NonRecord, - UniversalId::ArgumentType_None, "Region Map", "Region Map", ":./region-map.png" }, + UniversalId::ArgumentType_None, "Region Map", "Region Map", ":region-map" }, Params{ UniversalId(UniversalId::Type_RunLog), UniversalId::Type_RunLog, UniversalId::Class_Transient, - UniversalId::ArgumentType_None, "Run Log", "Run Log", ":./run-log.png" }, + UniversalId::ArgumentType_None, "Run Log", "Run Log", ":run-log" }, Params{ UniversalId(UniversalId::Type_Lands), UniversalId::Type_Lands, UniversalId::Class_RecordList, - UniversalId::ArgumentType_None, "Lands", "Lands", ":./land-heightmap.png" }, + UniversalId::ArgumentType_None, "Lands", "Lands", ":land-heightmap" }, Params{ UniversalId(UniversalId::Type_Icons), UniversalId::Type_Icons, UniversalId::Class_ResourceList, - UniversalId::ArgumentType_None, "Icons", "Icons", ":./resources-icon" }, + UniversalId::ArgumentType_None, "Icons", "Icons", ":resources-icon" }, Params{ UniversalId(UniversalId::Type_Activator, "a"), UniversalId::Type_Activator, - UniversalId::Class_RefRecord, UniversalId::ArgumentType_Id, "Activator", "Activator: a", - ":./activator.png" }, + UniversalId::Class_RefRecord, UniversalId::ArgumentType_Id, "Activator", "Activator: a", ":activator" }, Params{ UniversalId(UniversalId::Type_Gmst, "b"), UniversalId::Type_Gmst, UniversalId::Class_Record, - UniversalId::ArgumentType_Id, "Game Setting", "Game Setting: b", ":./gmst.png" }, + UniversalId::ArgumentType_Id, "Game Setting", "Game Setting: b", ":gmst" }, Params{ UniversalId(UniversalId::Type_Mesh, "c"), UniversalId::Type_Mesh, UniversalId::Class_Resource, - UniversalId::ArgumentType_Id, "Mesh", "Mesh: c", ":./resources-mesh" }, + UniversalId::ArgumentType_Id, "Mesh", "Mesh: c", ":resources-mesh" }, Params{ UniversalId(UniversalId::Type_Scene, "d"), UniversalId::Type_Scene, UniversalId::Class_Collection, - UniversalId::ArgumentType_Id, "Scene", "Scene: d", ":./scene.png" }, + UniversalId::ArgumentType_Id, "Scene", "Scene: d", ":scene" }, Params{ UniversalId(UniversalId::Type_Reference, "e"), UniversalId::Type_Reference, - UniversalId::Class_SubRecord, UniversalId::ArgumentType_Id, "Instance", "Instance: e", - ":./instance.png" }, + UniversalId::Class_SubRecord, UniversalId::ArgumentType_Id, "Instance", "Instance: e", ":instance" }, Params{ UniversalId(UniversalId::Type_Search, 42), UniversalId::Type_Search, UniversalId::Class_Transient, - UniversalId::ArgumentType_Index, "Global Search", "Global Search: 42", ":./menu-search.png" }, + UniversalId::ArgumentType_Index, "Global Search", "Global Search: 42", ":menu-search" }, Params{ UniversalId("Instance: f"), UniversalId::Type_Reference, UniversalId::Class_SubRecord, - UniversalId::ArgumentType_Id, "Instance", "Instance: f", ":./instance.png" }, + UniversalId::ArgumentType_Id, "Instance", "Instance: f", ":instance" }, Params{ UniversalId(UniversalId::Type_Reference, ESM::RefId::stringRefId("g")), UniversalId::Type_Reference, - UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", "Instance: g", - ":./instance.png" }, + UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", "Instance: g", ":instance" }, Params{ UniversalId(UniversalId::Type_Reference, ESM::RefId::index(ESM::REC_SKIL, 42)), UniversalId::Type_Reference, UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", - "Instance: SKIL:0x2a", ":./instance.png" }, + "Instance: SKIL:0x2a", ":instance" }, }; INSTANTIATE_TEST_SUITE_P(ValidParams, CSMWorldUniversalIdValidPerTypeTest, ValuesIn(validParams)); diff --git a/files/opencs/activator.png b/files/opencs/activator.png deleted file mode 100644 index ded6ab835b801a7f9b1972cccbcb57e54948999d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 562 zcmV-20?qx2P)2ail&r>6(So8D<98nKcAJ!2|93wA=j+{bP2-o?~!8moH=lbf;1&Gnq`vWIbRESj5&K)e*-aT_RMTz&k^i zW%O?J!(cF25}==eVXy-pfI9AfQ{)OyGq?dZcmz=jZ6Xc$Ihw>urBWT{890KA38yZO zpl9q}unUp|tRvNdpTw92eQc#bF_%+YphG=Lw|M)(hRbRnxCCY!@>!DQeuP#9v{-|n zpYpmBT{cpFl!p-ukV8OzVTmGXJs5rzdlA@dD$i;SCMlW_5_bWbKo*PxDHf)N6q8FF zp!+!q)tDKOfLjwz9#S7jvJn5 zyd3%nfp_g}!88a^P@b}Iaj-zLOrnk|pgd&?-5p#F zAjIXxd8xG&dH2HpT%$++%2V!09$5>??Rv8C-;>S6d-Ukz*8l(j07*qoM6N<$f>?F# A-v9sr diff --git a/files/opencs/activator.svg b/files/opencs/activator.svg new file mode 100644 index 0000000000..11a23003da --- /dev/null +++ b/files/opencs/activator.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/apparatus.png b/files/opencs/apparatus.png deleted file mode 100644 index 4e95397dfadb913e58762e6f3607a6d77c7da01b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 364 zcmV-y0h9iTP)m?Q`zu z8)G)TQm_4kc4NmOp-XBlfgyN1tT$$QFh*W9#`(eZcOOZqlLr5hR~vAIOB1-JV@mQ? zoqEz^w!1Gq--3FZ4L3C&%4IEaWC17s8!KH{AkWV6W1X7dZ|xUVC1G+8T!mf$0000< KMNUMnLSTXoZj%iF diff --git a/files/opencs/apparatus.svg b/files/opencs/apparatus.svg new file mode 100644 index 0000000000..1345efee56 --- /dev/null +++ b/files/opencs/apparatus.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/armor.png b/files/opencs/armor.png deleted file mode 100644 index 6f5cc83c592493c87acae0136c03088f34f25373..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 473 zcmV;~0Ve*5P)_DCOd#+$Fb7 zD6OM%K}pGVZ#!F(gM%Y+Q?AHCPLgjke*a;%oozJx)Nh{e=Y3}8H66$C4a77}sj9{I zeM1=GfT}yo3e}sEZ`-!?r?yDiBsi7gLZJ|>oKnDb-7kh{r8J2b4gF83OOtz2UcFXD z;dg}}=W1IWScCr1fD4>1Y3PO|X`|o@+NCw%9(te%bC4ts1?Ql{F15}9%)%6Ca*~Ww zFamq9q790cWexHnAJGlGPCc<$Ay-GDmD+3mRxkF6h4bop-WkaObdF#?w);1Dgj%F_ znm=$uucr+p6P$iXUb=ZP;Sc44BGc?y?ufh<``h|gnuMjvS8ZoqL5nIEXq2=$| z?qUx^2b3hTK^SF|V{BcRet3pcxMOG^IwFw`!VHCK)Hl(+xP|4oX5z*Vn$nOwhO&^k P00000NkvXXu0mjf$y3bG diff --git a/files/opencs/armor.svg b/files/opencs/armor.svg new file mode 100644 index 0000000000..e7925a23bc --- /dev/null +++ b/files/opencs/armor.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/attribute.png b/files/opencs/attribute.png deleted file mode 100644 index c12456717990aead1a152c0868643699df2b9afd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 326 zcmV-M0lEH(P)U02@)tmpWqV& zKTz-mo~?9d=M4-M*=)xvh~%pO!h0A8SluK_nZt + + + + + + + + + + + + + + diff --git a/files/opencs/birthsign.png b/files/opencs/birthsign.png deleted file mode 100644 index 861dbd4d108807eb704333665f0542a49fa99226..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 444 zcmV;t0Ym8|8w+J+ zd%?<1Y;0UXNFvw>TG-f#wGUvewI=a9F*n?V2;#ux%;cQ;yPoN=APD;WzVJM6Qgl%i zZ@_ArrccUU^W8)-#RK2>9|+fzB=O|LA$|k53pPw-Z3yN_uJH+`6*W->@b82xaF3R2 zZ*T^&(N$a(EP$7`Sm@F9kd=qrX+V=@*%%EzU6C6{QG~QGypPHAd`}w;33hZV#VY)A z3)(T-Tw36OfydflK(`|atF9#~c1=_5md*xS4HeCnDLTi;IU{!)D2D$4`fx9nYc!}t zr{E?&gFhoy;XLsjenP$v)}|8{x#d=WxN8l;h=0?hSDZ1W|E&B9Y{T8R^+vBMkt__u z0>Mx6%eSrX=|QaN;dtRPZBNo>BHDU2n6+IwVaYbrS#cbfZ&YnIqKO)G>TO+(Dzb?k mU~?CHZsNv&p5zw&C;kS%t-~3F{lQ-V0000 + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/body-part.png b/files/opencs/body-part.png deleted file mode 100644 index 8baec720247c579b9f3dd1b1e36b0a1226fca66c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 418 zcmV;T0bTxyP)hY0fIqb2@_aCwg-TT;UH=GopooqTUfa_`F8&F`@Ws~$9NM~7=};Am{s{q z?6dMb_XKI077eImCOk_^d0i=rsE>AqdqIsekhvh0OP_U!(6KG9%6JRu&8 zi|o48fGMtg-#1KAvqg4ZY9K_maZA`#kshel{~AEN1RE-lTVSGTtz8W6gCLkv0R}Dd zs@5(BnUV+bj@Zy%**h3=WNcZCK2JCUuTsl$nc#wh+%4$dKC#Qq%we`vY5-vcyg_Pl zormN{2)D_ + + + + + + + + + + + + + + + + diff --git a/files/opencs/book.png b/files/opencs/book.png deleted file mode 100644 index fdecb1585abea4e4e5b21014be31d4bbb2f45465..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323 zcmV-J0lfZ+P)l;uAOAr)6aNuK@efw=DJFC>MLrVj`?+-`|t#uu#EX%Ic(=<)@ z5AKpTu$s*^<|FWiW*E*7LxUW#*_YZFb0UTCi`008uTfP6L11LDDA;ITabcw*<2!4A zaDYrfE>Xs~JhMlq=g8%79Tnef5#nkfgc6l{zt~In(p8E VUU1*+fjIyG002ovPDHLkV1k1HfwBMq diff --git a/files/opencs/book.svg b/files/opencs/book.svg new file mode 100644 index 0000000000..b34827d7b1 --- /dev/null +++ b/files/opencs/book.svg @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/brush-circle.png b/files/opencs/brush-circle.png deleted file mode 100644 index 22e92c1c7f85ebd8e3c62d7e3805d0015dc180bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1540 zcmV+f2K)JmP)WFU8GbZ8()Nlj2>E@cM*00n4CL_t(&-tCxwNYr;2 z$6w#?T|wqNoriYKGVL&>8AWT0(IDO0g!%*<2}>&2A8pwm$-@1haAOPmu|kqD&4MvV zgkco;vEgV~8^mZ4NiH-wbXP~Foal)u*xmQLKl*;hA?Nwy?(`bI;ke`OzR&Z#KcDCG z{yfhE3s}GcUT7$@c__2ir{sIP)~p?HZCf_#En50)bfZPnQUj_w@N`kaohJ$QmiwvB zYwh-&2kugv9ybIsYkjKdzP@Wif5$}yeeqQP*HOmrrseIG*#(!}r8aF|3G6s_J9lSy z>kX?GNCyZg>X3IyO5@|iwAP-aj5c?Y!;?AOn~^o_S-*UwztBE5SZvX?WB|Wv>1)a^ z+}d2RsdHWkeDY=UjyHPm$>+gXdtJ5_m5n(?=XwsN1SWeXt7gzr+|_cx>3ek4s`(uN zw;bz4-N_xzb4nmM_`ZbH3k|v354V?Pj7---ezhmDr1NT3`sl#^SddVrN}CT{*E#@P z$tyoQUcKkcY#kjbt1ovBJvvA4%{4$E)|8=}Snx0I;*W z^+sl`PmRQ$5JCw{Ky+W3g%Ab6#}i0h2}$5dg1rS``AC0ZEC`r(O+V8}p+w-P z|El!oC6ET32ua|6s`DCveQdBet7gy=)zz;VwAjZ6i-Y}y3EcEu39K~p-awO-@^(l9 z?e?4pI?A|3*OH67S`I{$Kyg>g0gJ9B>nP*x_M8Xd4z83kAcS}wI23#q416)^8E}`{ z^t)+!djXujM@RFkJ&Dl>KEK+N==4203UD_qZ*RDE)2wxXAA(ZHOrIa6lwDzqIhkE> z$*)@ateW3Z(s{Ki8U#u@uU1(#zr(Lu`eb&&rKx072=OWK+Jy2y08~v`PPdPYu{zzGUx!Ke-WfyLpW{=zs{5GL?IV+{SKP3Tx=88?7w;byPfOF{K z=@X@O=VzKu`PH7p6Qyg^0e>6H zgQ5Sw16fkaUrmy6JCzVZ>;@VpeEqGI^6M93E+mBb6WAQ2I>W$5DWxlJCv!pwRZ6J| zA>IQ1Fm77Kc)cy9Y&HKA{G4@G)nutncSC10bOPT;>5j7)L03!=m{U^9R^x8aqOt?A zf)L_O;AYTQH>H$YW;IrsCmag;><2y!(GSE20U?AM^w}nacm?>!(0hQ7q?Ddu$&6^Y z8|C25%W5@HV_Z}KKLfu2b}8kw$`@rGci?5G5(p}j(}1Fom-ll#j7tJRYH~Krne+oE=^;t42|~z + + + + + + + + + + + + + + + + + diff --git a/files/opencs/brush-custom.png b/files/opencs/brush-custom.png deleted file mode 100644 index 58c0485502842819d68420eb1e6528d9a923732c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2475 zcmV;c2~_rpP)WFU8GbZ8()Nlj2>E@cM*00|FCL_t(&-tAd=R8-d) z|Gme|zzj1Ci-Lf_;1UIKX0GRd9QzTUOGi*aK??(<9E(G^WJ^;efRf!_kO?c zoBMzl_QD>^m?!D1|0a!mbj`PuBMsUqBF?5TNPI7scI~_8z?y{OPEX*qRry0kY0mGK zT3ZJ=`sEnbaH-2n^EUlqa^}+@;Ps@ykEuCJy=`s1IEFRprT$qpiqPFMYfFfPYg2`o z^%HxIo~u1~?%=r=z=FnK;#Pe2yMwOiKZ}G zm?<7it6A(*jI^9IfkZ!jWvs$tO5hmQc(H4r)rVG%C>4B^E{rg6n>4AjFa1p=Z!^cg zth=@(6DWAcO2S!0N$PXS{qz-USe}z}3~R74 zUd>|88p1PZtCI$1?~a?YT%D@q+xv^!-o(znbRa#jd7y?9#} zVRml>sNUU}F@4Kp5tx~llbEO}JOzN+*LOm6&dPz+01&!zn+pT|nS$v$n{6oiQ z>ZF0$dEJL@&0RS_?QEcSBmjWt!L+rAB-gC0s9s?)jpo6$7Iur9wqpZ1>~ReAY`QfL z0Am!(^2+>G-vO$c7huD-dv^g8RQrV!d7IhqzXSqmU4aJxb*hrvwY_O^a%FB&Kuc2} zq5Zfje)ByaKf|1h2lx56HYX3PyRk88U1^E%;+z>FgrSu34-inc8V3VFtt{w7`{htI zFThp>-1ao!)Tv5t6Ob9Ty@_#X;{hONZSk_7R-78-ZL{U1VCxrVJR@A~VtQ7!Cf@3ZKz~%X2=EngawY`abrzA5gu%&6L#Yfit(_t}p?%ctW#+1!_eXT7TZ<|HVGmOzBmKIxCk%nWv zOg6^LAmVKSkp^wDQS5vA%IPoexC=h^v%AtauN>Yu;!a7rpT+be09>UqBJGn;U;n(l zWR{hc0RZ~+>5~hKr>GcQZT+dK;N z*w5}tQ?4EPD9of=2!OXSUiH@kd#p%LpZZ59RkM{4vJ-#`0N-IT=gjQuvU<*nBSg&E zy7COHo)8#cm7fx3(kFQhg!~Y|uiKHzcAEhC;CyV7`s_}* z_Tmo!Fpb^{s`pXWx#Oev);dWqcWnsy`sH1%(u-vmt3+0DW!#O&A_0# z>ysq5w&+$b$<;Gq-K6p{VyM1e2)j9ySIxHPQ z`u{{JEpwZIx6Kj_02Tg`C)@=;#qK)$QK3zi<5(cN(ORWWDnBIc~Y5=~JA%X10_yrkMMeAT|b zKi=>NLtX-K62KUTq?WYy|>nFEB{Sb%ftG;x8TbqJ?hI= zvpn}d6{S%u%h$+5_GkAVv&-4D4sU-cfEWNX0mK0~06+oYw;t~cjbD4A=WAuhD*&KU z8L{&3y+&_#EFpgJ{>hPj8>*&;nDuY6JSVT0`e$n7-HsV0vic^8LZ?x5yVc<1XK)7I zfrJ9UWxMjguK%Y2L{myn2qZn)*jksm^YdVH!y62ENv$ks=a(^L@kmY4_@MiZvEioL zH$BEh+ z^Fz$~IjN;tL&cmml;t@&%X12&SoV!Z9&+$lbiy7_z}szpyQI|&;IB_~YjNqK{c~a) zDt8Fi_k8>g8N|Lz)(m{7 z(>)XSR4c;^h3i^bkhWPOH@XYFU2VfT1gTIZY@n2$vgZcwsqRict`R~;0662&bcRwI z@9G&Aoy+DsoLd1*6Qs4%5g>%H4(CcjNH~CM0FCzMS(H+Zqhvny4;*fQ7m`&^0NVhJ zwcpwg;3$ADl+yHP`hmj(5`h5D1L$cFUd}EmS^z3aspV1pJkJ=IX$LRh5EL;Kzz2`& z3IJixQxCulpcueK040>tYykII3WN4002ovPDHLkV1mV9x7Yvx diff --git a/files/opencs/brush-custom.svg b/files/opencs/brush-custom.svg new file mode 100644 index 0000000000..9eb59597cc --- /dev/null +++ b/files/opencs/brush-custom.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/brush-point.png b/files/opencs/brush-point.png deleted file mode 100644 index 19b2354f8c2dd5a053dc937d8fcc004d2d855fc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 787 zcmV+u1MK{XP)WFU8GbZ8()Nlj2>E@cM*00MVOL_t(&-tCw_Xca*a z#((#cQV~=H5hRe-QZz^`1W`e!Nar6&Fuegm6olA_!B~Ve3L1DI+E@krKoBHd5ELOO zX+-c3qLmOu4TP6Gn^{>NFE1}}$=lN#IQI7TZf?HanQwL$N-3q3QvM044vp2TLu2*L z{%@t|6ITK#0sOB3%q%N307>za6u4X{8V!``GUe^^nk%rL8Cq?ob8@FHpk`)wfga%Rzp?_c z&Jke7yLGpHIxC3rTY!0BZ)E%m&{s*^@Gif-;l?jV!;dAs%nd-^1&x_yl9tWv05B58 zLXa!D<$(>}>)ej~KE!QR(%WQdG|;A*T?F<=Lz%nLL{aj<8{k<3V@^t%PZogte%;J2 z0cU_g;25yrvp<@2LF2}61ug)?z!y(m2Z5847URsEstcWR+6;0JI49|8V=5 zlKP8utTGba*MQSa>VY%>#Qtn%+stea@WJ^W;Jl>IAup_^C+m>$AzAeTcRW!6JOZ8o zJ0(rDqbuv+3Ty{ncyYesS2yq#=#jM4j-F1&z+gC8a63Xy+6`Q9S4k%U9B(nUFG&E= zHhC9tqRA^ESU)8RAYzlr7BN`^ZYBdD=5RC1% + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/brush-square.png b/files/opencs/brush-square.png deleted file mode 100644 index 08628772e7c5f5fdb2a97335e2ef9e597f913419..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 761 zcmVWFU8GbZ8()Nlj2>E@cM*00Lb}L_t(&-tCw>XcSQt zhQHaQR0I`41c{`z6bTXwLDa%ir1Jq0Ei4xVQ4k`C_=rVFqhN%fXk!)d2O%JWMH)p2 zJ{l2xAXo`ed_dTT%^A2X%do*@=U$l~mYJQIy?g&T=ltjX7b;Y!P@%#^Z!S(+c zutU;(PBP{cIvh+MU-fcsHFWhF*nBk + + + + + + + + + + + + + + + + + diff --git a/files/opencs/camera-first-person.png b/files/opencs/camera-first-person.png deleted file mode 100644 index b497198df53e72097e982ae69e8515f04691bd22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 750 zcmVaEbKv)>L=(;a!^5v2zvAj z1QCQD1@Yp^o4pj`p{LqwrFap&^_H802L%zEw9>@1hmB2}-Pz|M4aPQ&jcjxx`-eTu zn_=Jk&ztw=y@41pV#N5DP{aDMhya}48*VA3^hoT8h-5`%Rz$wbPwa@u6A?LOy7A*8 z@=*>)MMNe{Ca@yE$2(JvpXz6|*1CImH7dPMgdFbo5|&CO78e&urBcky%#h3F0B!?s zqEKo@NWk7N@O_{8`FR?R1{)h2EG#T^rY=UMG)e;genAif)a!MWQrNalv)SxSt(!<- zdA}f?PLt2)snu$1Zf-I?J>8jFGBJn~BC^&icq^5PRIAmFXkA27re>n^BJ#CQGOCHl zxhQo;c7)ubmr=9cjR(uyFIK;W2j)LlHIH6yFR7>Ab+_o9G_DqMi{4}9q#mvXKH3k- zXW&|{otRdtA+H)jGGUQ(93*HxD;Kf_)9!&u(LNJ|kbqA5U_Q7jlc<9c4Su_ezE?`yC_ + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/camera-free.png b/files/opencs/camera-free.png deleted file mode 100644 index d8e7ccae5d2f855382885f1061703b259d49ca28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 783 zcmV+q1MvKbP)(}D*$c+=mIbg;GGa6<9W3tkC6OGYsN{o zH<@oA$uP;YBm-_2?WqS(VovFPc%|jTN4^?S3d-UL3DR_qf{zEQ55)mKGT$_ zQ~VnMcN&=M0)S5>4>+~@$E}uWZEcO|bej2mzIwP^BgY7vC*`Vu8sux_$l3+&?Ch{u zEK=A1d>RJHgHDN9Z&1L3MeHfNpp}&s#^Z4g4Gl4wOtM@qo2C(`zJZqw`i)zRowf^_ znwrA)_BH~60J7OEW@ctg)1XrV9s~Gd<1;CQcwsT#WtV(;c^QhL0025VIrXd^-o2GR)T8VQ2E>>O*6~JpD zgkHzL9e@VFU(3fZ3`|T+pja%TzrP>7y}hQXPY5yRg*N2L9=6f%Uh1RvlPokTV9;yL z;H4(Eu+8$yWIfTqT*QDm+8-e#_lgRxZIhA(2QV5{aZfzX7uA{Hv>(xTpXC N002ovPDHLkV1g$)RCoXY diff --git a/files/opencs/camera-free.svg b/files/opencs/camera-free.svg new file mode 100644 index 0000000000..ee1ed5afa0 --- /dev/null +++ b/files/opencs/camera-free.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/camera-orbit.png b/files/opencs/camera-orbit.png deleted file mode 100644 index 1aedf2847971c5fcc02a1083528d20aa56dae115..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1096 zcmV-O1h@N%P)xK6 zs``+so>A3`E^$RwUsu)b|L7G^)qdd9RYInK2Sj9Yt+8A`Y^l*u4m>Amr~y^o0sI2k zYlL37rYJYh&Hp8|6SxJ~2iyhh1g-{JYWkFc8Q=nN8u%daoSCgQa1wZWJ+bG0jt<@N zSvhi>=mmO#Iba%y00nLYihv7j6Jrais48MvXdETLG2mF>IhVG|zzk3~$0sKznVXxV zx3`yKvB-R+r;7z^U>#{hx)OH-j{*n8*zFoeh_TT)`W5I2JZCzS0ad*|&F{Z4F)_jD z=qSb*`uh4985u!DeiD(L8_j9g4=vybB1Zv4EHsHv1)lR*y?AQ_V^61fy4)CJV`H?n zwb9zzN~u($TrLC5Ze~{rJSPb}=Vesyrz##SYryrxYcm-Tkr?>8PCL!b%|uZ|rBb1* ztBaPF7SacC(*&L~P7=>l?_mJ;W?O-0XWOP`ld*c2hK7dd?(U|ir-#A8!Nncr?JNdd zKkPuHy{4&BFV0@)VEcjX9c|_vRow$TReQaoql4k$;o7D_5xJD{j5>~3pfRcmz&D#R z;QHY!z-@u&45TS@Kvk!Kp*rCq@Vtn;pKhhrcly&;ibTHjyAfc zasBXN;DlJl)g-Qpvurep`UB7Te5HP?t-y<^z3~PM-wV7U#@?+-T-`QBKow0QZ#D62 z8MqZyfE^7G-2~hRyeg*9qe+a$QSGeH0uKhBQ`&SbrpP0f`sG=xnQX^QFMidJX0R)W~04@VR0v`hJWfi^Y_W{=r z4X`JD7(0PozzskXFrWS|1K$A`f$xD*;5onL-%oOKa&mHVa&mJ1d;AG=w4*5!t!mr= O0000 + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/cell.png b/files/opencs/cell.png deleted file mode 100644 index 4c3fbe25154dfc8dfb1e6cbe8422c1dec6d932ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1212 zcmV;t1Vj6YP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O1Yuva2`@{nsjb36|t>ImSa(@1U38$KXp6LT(b? z!v!*mCZ8jlQ2X!SPXFK%F`ldo(I@W>E}LwUj7c=xwMjO1ea;v2GkMU{{)0fJkh;D4 zHtjR??&c8e)Gv>E3T4KLGtf`u8Bj-N!DQOS8AxT*^L(c?{j$AV&b&3OA$H^AyA1%N zIw)00nz9-^Wn@uQpiwhqTAyBqMlrXL8Miyfj`ryRynS}S&g7}rM<~enY2XhSO+CAF z>{f&hto>wu_#s9U%>8wTUGFva-nP|c>w%P~xu~C|tTPxcfKpdCj}dS?GeVD%Fd~*% z0D1+R3q~PMEI_-n<;s~mw^bG{DA7uj04Y*LS6$Q~Nypi59S?P|IEm zuX)p&uW&@>%CHnhFows|pflx_u?OmXv@WxWJ65PmMgyXZ!Q6Pl3WRW47wHOhy`Y|Y#|oMx8Bl~XN8?Jmdw`f9O?`Jh>+W%7*;S~Jerw3n;;UHfnx+e z8I_A#S-=20CzYv#8^ult5@qCuPvF&7oNQ_wEg-_F*8pm;bwHN24*e5sL=R=B&XRM^ zyWpZrE_?OXJMVq)#VvjbOI*^D3)-jH;)*Y!#F9#`Qnh0HS6>6wSX0f78(OK^=9+Jz#gt9=4ep&H<+VA-DK{W zw-eTCUcV^15;S(e@>p)%Yn8;EYeSnWd$*)kOH!y!J>3e?8Yo|Ak-L`dc z_)t&D{xlZd6!zc&MEsxjQUAEkJsW+|btmhMJ zH|)9}Uer?D&iB%^Yt`n%{dsQ=r`_&&^rH_Q-%5Iy=|!TW-BX~yR?(;OSm|Z3zpm(0 z%r1ld#~8g#L`PcjcQeudxU@{m!+ds<00006VoOIv08Ic*08J?m1UUcz010qNS#tmY z3ljhU3ljkVnw%H_000McNliru;t2)|DG{B3eS81_0DVbBK~y-)?UT_7z#t4nFVb}i zE@HNEif*He6dWl1XoV;dZ2j%~G^P{>^MKig3jGD%Y#}y2|d& a1zKE!{xaOcA~d)F0000 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/class.png b/files/opencs/class.png deleted file mode 100644 index 272f2630c73d086f867547ae82e327357ad42bab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 588 zcmV-S0<-;zP)S5pWQIE9E{VUbhuDoLsnEUW~xE`}%) ziy+uUk2Fb<%D+GaJ-I6^G=X5D1n;g&5mE>u3N}F`l89ckQWO$`g}Gfn-(}x&Ug5xp z`SE7v&3n7gwra$6-8Mq+;kj+wty&I(;2O06xl*ZAR?LWF3~7q<6#d2=hvQUCG^)GX;gn2tue^W9CSDt%`?s>y|i zVdOlC?eGlr-9JGiBYS8aX#@2DhCu6U(yWigw?WcAV|6pCE>i_*{6~?0j=u#%>}(U; zGxrg{1p+C~q!%aIhOR=PU>=h`vHp|46n`+q_*OiD90`-?H+~1BOpu5sxWfMkdd`RF aSZ)Cr5YXn`)Nl6y0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/clothing.png b/files/opencs/clothing.png deleted file mode 100644 index e42988e749a5f1fac8802bd485d421ccb380e145..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 369 zcmV-%0gnEOP)GZP~e*mBJJhvqMVpmy~Ss8eQZ51Qu6?;c>!EJ0!{6y6|k#GrD=A4sQlsaD% ze-kD=&Y>up~P!=?d;Z%3z$G?lCy^<`Vwn zoH*y?!dmnPRObS{xTHorwoLYCj>UW(wtIF5aZY0bRqr~%-`_8O*9m_IbCrMZ=6^?S P00000NkvXXu0mjfT;7+t diff --git a/files/opencs/clothing.svg b/files/opencs/clothing.svg new file mode 100644 index 0000000000..9b1ffb54b3 --- /dev/null +++ b/files/opencs/clothing.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/container.png b/files/opencs/container.png deleted file mode 100644 index 336a05dbfed774ed2ac8e9c425c983ec216986fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 421 zcmV;W0b2fvP)bh=@ z#9mPprE)z@Oso*?=Xw5Quz8^+X=2QpZ#VQG29gMfOI5hS*`%1?{dCu(D^_2(3>5J-h7Af09NZJ@aX;pxdPn$ z8<({GmkZ4r+NtVX0f0O9sS + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/creature.png b/files/opencs/creature.png deleted file mode 100644 index 003684dca0bab3c8eaa63de379fb7e8698db2b9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 463 zcmV;=0WkiFP)-!I!h5dZSNYvLa@_9un?@R zEUXkHB1E(>g|WQk5GyN7!CC_8?Supy13ABUW@QzHr~{jMd2inA-0hw*{|wLi<2bH) zp0~v^!{P8S%d#D-DgUJVRwU22+ikDaYN_cghI45O@Db^&ej%-xAW4#3W(pfju$Jff zL=XghCQ*&?ecz;M`bmfDYqO6An0(BI13U@tsi_*!I9DH6r$J8*ibufkL_tO_jZCND zy3C_EBi}gsG1J{k(^zxlCM#u!ZIKi<9&f3oYkj%PGtI{^PR0z-pj(pd-@A z#Tv)Yqv=Je^hINCuxb!f9O0|jae|GXsoM)Woz6q0QVF?vw^0<8!H;mg@0wtmLEo=d ztJiF}tCK5knu-x4l0KjnZrQXrH%4P?*9NPGx$EkG#&5~!$U^oL`&a+~002ovPDHLk FV1o0m&g=jH diff --git a/files/opencs/creature.svg b/files/opencs/creature.svg new file mode 100644 index 0000000000..e7fe8cf5a3 --- /dev/null +++ b/files/opencs/creature.svg @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/debug-profile.png b/files/opencs/debug-profile.png deleted file mode 100644 index ddb01da4373c24e073eaa8780f2a1925d8e4c291..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 431 zcmV;g0Z{&lP)x>!fO`r}B#0U}7m4vO)L ze%-f*hFHKny78>*7F6o@9Wyw`w8k^_qV`xoD_*dn!6nV&UgIg6aviSFfm>|H0>X%5 zQd1npNnrg&n7}Hkw7k!d?}ls~p#o=dHZco_+K(su!yk}`Ij#M#W;5-=wLhRn(*6;! zg%6!v)B=)qB(|seE1ch2 z2}Yb(WEZv+3nAUa;B+8qvZK_S`?_ojxuzu|$ Zb6>EKPtoppBdGuY002ovPDHLkV1knC#q + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/dialogue-greeting.png b/files/opencs/dialogue-greeting.png deleted file mode 100644 index de6b22b42c4a025ab1f1674f20b1e7dfae20b3e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 386 zcmV-|0e$|7P)g_X0E&S{nb=LSc*vm9boZbGbPdA>kqglYIGRXLjbko82{06uAI1#t2n5=kl^3 z&<2$&y^T_9z4gD`og38jvtbxInZveO=)b3Fnh&jPK%;k(Z4y&$x{3MF5%DL@`3)`! zo`5Nv4&VW5j%6c_pP_D+We;#4T!7rl8IZgq(xLbU{sI$`Sx7x&eNXNdj@Wwz=jJPr zz#*7{As7R-H=GfQPEpg+pm3=`7IdBi8O|qoiR0K6_vDrJBgJ*{ANWh#`$aOUVAJ^{ zSx(>wy-lQl`+FrPH8mVdvUnNGUsI3CU*j*}hQ#y&X};1EERMEkt*s|XvQo2h3flEA gqwt)V+>A7UK)-ht$8>{E1Ew{Z+ zIBXzN^;~9d=2DJ5&G$JXIM1BoT3~ux%beSpqjgn9@VTFs%nA>qJYDk1=oix!i_xbJlLj`N(U)D7#Qcs-tSlrx}w> zIDfDi6f?2Za_oq-Dwn&UA%9=?q1+PlyLR(5!#Ef|G3*havwUje67@fE56gZ0%O7jc z)UaFp?ZMvn$vumuihcHozUnIwKUOa}r6*7QCU@zL`0JSyyE=jXW$<+Mb6Mw<&;$VZ CHFqQc diff --git a/files/opencs/dialogue-info.svg b/files/opencs/dialogue-info.svg new file mode 100644 index 0000000000..a0b4f4236e --- /dev/null +++ b/files/opencs/dialogue-info.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/dialogue-journal.png b/files/opencs/dialogue-journal.png deleted file mode 100644 index 086cb8a4221c2db4048edb3ab5e1251c3f79514b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Ftc zi(`m|;L%`1zC#8)K8w0qXQr#(Pk5}`F)@Bgt%PtAW9#m&?wF|79G6w+Ha`A*)UVcS zrG?IW+lAFrjh0)gH@)*Ip0Km|o6;hojm+$C=10FOI=A_Wm&2k$nb)=+7QUCc-@OVp z?B-^Yp5VJEkJW2aMrh>zH?2&e$@~$f$2{HyUs!SFI&BN}%=1vb-z^Y=hR7j!nE8PTZV#1@3uBGL- z5HI02ptm|+WCDbLxZ^smoU5FmEO-?w`!CeJ1^M2w8%+kB>gsBRlbQ~WhBPmux*x;a zlD&W*!X`=Ly*e+bmOnET@9@5~*1b56y_!}t;Dl=$?CUmo0KSf5wfX{!5C8xG07*qo IM6N<$g2phI0RR91 diff --git a/files/opencs/dialogue-regular.png b/files/opencs/dialogue-regular.png deleted file mode 100644 index 933afc595220dcbe46ba10a4582325a7907a300f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP_WYcr-bcN;5 SZ!iHmjlt8^&t;ucLK6UMEmtZ4 diff --git a/files/opencs/dialogue-topic-infos.png b/files/opencs/dialogue-topic-infos.png deleted file mode 100644 index 6242eddf4f0858ab3300580784fc9ef193e42c11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 306 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1FST zi(`m|;L;#NzC#8)IZUq(o!NXTf5S(yBPaAH+4OK4DhR&o>A7UK)-ht$8>{E1Ew{Z+ zIBXzN^;~9d=2DJ5&G$JXIM1BoT3~ux%beSpqjgn9@VTFs%nA>qJYDk1=oix!i_xbJlLj`N(U)D7#Qcs-tSlrx}w> zIDfDi6f?2Za_oq-Dwn&UA%9=?q1+PlyLR(5!#Ef|G3*havwUje67@fE56gZ0%O7jc z)UaFp?ZMvn$vumuihcHozUnIwKUOa}r6*7QCU@zL`0JSyyE=jXW$<+Mb6Mw<&;$VZ CHFqQc diff --git a/files/opencs/dialogue-topics.png b/files/opencs/dialogue-topics.png deleted file mode 100644 index caa6d7e7cb15c81562831671e9c04d5e06a4a284..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1FbW zi(`m|;N4(PzC#8)K3v=9Rfc`y^V`E(=w!3_rxeFZ_Cr2LUEVDd>5JI(#>6<;ys)On z(0a!Ad5N2!+FY13`9Rt-#!0)cM>%a14t2bQh$+S(6oCEG2WVCo7-OPbundXxZ0Wukc_xTgUSQI~(jL$`u^ouiG5)jz7G| zaV9VSL6bLgAKLDD8pU)bb#)4(!#)N3L(w5`cCV8zT(!O7o4-Q*!|zL1uHkApVeT|h d`t!=YjE_44d}XR8wE_Lb;OXk;vd$@?2>>MvZcYFI diff --git a/files/opencs/dialogue-topics.svg b/files/opencs/dialogue-topics.svg new file mode 100644 index 0000000000..57dd0b9444 --- /dev/null +++ b/files/opencs/dialogue-topics.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/dialogue-voice.png b/files/opencs/dialogue-voice.png deleted file mode 100644 index 1d67745e55293ba97bbb01e45f0418ae4458f278..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1FJQ zi(`m|;M8DmzC#KEtc&tus@D|pmd`u7k=gW<79 z27A5>?Q++YpIMxBVXexd4Cb@XLmCn##5T?}QT4rK(WkGvUeb)$<;?A5XVYmJJP`*} z&Wc`-kkt4%Kf~(4#hmL$y0X_;9k839bHHV#_PUKm35MKty*&N(9gJ3Li@s)UD4Du( z)8X$8*BNu~&(xlgVdlYf;APVF(~sQd1@2XR$9mRswtKSL)<4mWKbqc1INew}rRSJT q@@WRyc@44A73H#XXIypt!M5&q{5KJe)&4*~GI+ZBxvXn|be6?^HEU#c@1Bo-3uA zGA@eZ3z}tFwv^s2r>aO;QWnS~y_N|K}y$Tiv( z{06qbmOTM?xj`L+E=NCd> + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-instance.png b/files/opencs/editing-instance.png deleted file mode 100644 index 7349e8f66d16e18748f4e8b9bde1c84fd442395f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 986 zcmV<0110>4P)CV&*+YdCX})M$nSl?H356&sOe$6?D28}%+6R)*OMzJ!l%|CHCNY+=(afNoFp*TH2ifNg-ujvj%#1t&W zUJOJZ48l4Rrl*8Y}TAHezVWx7}st zJ2LZlMD)To%+Jj2%}nAE=-o(>nI~{J&f*T7%*=5mp*P@OY>9}^@d_5>?TDD%Oa|7V zEi(r<&<(|^%xo+72Qe};&oy9bY;kOCOG`^zS6A1B|I5Uc7s){1M(h=%Hez~>cq=C1 zqXtZUTrLsuGX^&+gs#T^vS8I>0~TcFfg0I+nDH-+uEzH`*(?Xnm1@(!#)#$Eotf+E zW_mvcm6oEB@w4z3cA@jKI}j25(S=jErpO+`z|4I4ABJs0dxLki;;GDRFNNVuGZ^TP zA4_ZxKfe-Z=JX4Uc?VB6kgvd;@_zt+YlZ`TN`rP5LyB=vUuZyC#D0y4VKuYA3_EM` zrC(iELU{OLc8d#PL^;vU?XnTu-l7u9B8Oc@$s~fuZ}j$fgf-~DW#)JFEc$N`r>vB!!x+4$PQzBX(PVEH<+E7 zN9ywGL{l8d%pDOi6I(K~Eh1(_#Ei^bT@t&t%CwY4?>5|wwi32uBBHY<^!ngPv)k#3 zcmXr929IUt;R`%JyR;6oO0P7xRI|kxgBjcyy!OWag zM4PcWB1Ynk!pyYp+L5(l6@JIo%v@Hdf1qq?#?|ea@58*3yxTH!Nw;n4ixY`f3_l-A z#rS!cn3-oHVsuIHu~>&qnc2}JP9;hy>_k^)jwmbio24^tFQ*iTurxDw_sC4O048i}|4DQ~&?~07*qo IM6N<$fb%7 diff --git a/files/opencs/editing-instance.svg b/files/opencs/editing-instance.svg new file mode 100644 index 0000000000..92e20fade3 --- /dev/null +++ b/files/opencs/editing-instance.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-pathgrid.png b/files/opencs/editing-pathgrid.png deleted file mode 100644 index 0ec024cadfd143c477ae6067bf1833e3cd0e44d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2297 zcmVc8Am&1B0ZWnCTKwGH-0%Z{m0-`d3umnOJvP@>a zd;3Q|VjKto0X;n@&wumH+mPu8GVrvpJpkiQOF$qPJ_i_UoG8Nb zBmvV-g^ZN4os_b}DQYMZwx_*uq5u`_KLM<2z2tWcrKxz@utiEaLrU5H1R9=4d>d1v9k8rnfnfNOQ_esj82$iZ^*b7kh5@z*zi9c#Ok^#p5?wk-g`D*av(H^ai?FmSqEIt-k>*%d%R#p8-|_JAtlXvUEcF z!O!!j{HXx*&WMLbD-IQWHIg|2%LI1dN_i-Db5>#)}P zPry|`U#;~cTI)l=C%{}F6*vzao4Rbw-0P|n{U;<}zoGTfEH+z0y|6tSX(9tg2)wa6 zQV#E`&Hmt?J;s~b30MMr2#f~W0v9T!YU9ev2mA|21YQ9y6GDt6dU$$}+8m!-2?6oZGdad;WU=K#;iC9I?NY3z- z9lgd-<4eYFWZeKB?cj^ntoVFv=73{L`IrMpN1qVy{3`!IUJ<2P7ZI~O*v7#THW>$Z zaPIn<#F8=>w0n2;=vTp!QhI@{#+ZppsmI)TdZDXdNIx8!WjT==p{rIIQimv|*1O+7 z35)_dtEJ1T>Yt@P+pVJHo>1O+_NU}Ftm$>?$lAS*w2voT-KAjA420*g#`Y`XPR^mg zbm3I5Hg?jZCMmOTMd#rh?$C|)d)CwDi#OSwH=$}n_Z}UQ;-Fg=>s@!5&^Q=5!EXj= z%Udk!52R~K3tKOb3)XW$f1sCA%Hh8zvFGw_2Lfwnw65$_Kt*~F+V5URyY2I#uKXio z`xheYlm`2CPj(@te8!DN5imz76#-zs@pW5Rx<9aGQQM@iUdGsMspx$_m1$k(Y#!g` zZkNOgpiC(>sxi51?s_Ki^NVgPL5N%cV<@@!_5AK{zq?k)iI@ZQP)c11&}-V(nJN1| z8P|5>M2ww6b*Ed{eZkNpCBw7R$Pp2Dj0OA=7!NG4fFnQ@h`7EDz?crg%KiB1O}>gP z>>99`FRopLRd?j;+~QmNNhyy=DOUoy@#OCX-;oYI6}C4QW55`MZFk#$`IG;i6j!oJ zsk;EblyaMt@<_+!w+(bG@2>3y#e6w%J_!{iv5y6W5)&@tX0cXExfQ4ce&S9xRw=atVDO0BU)pi?)G<}| zcYWPw9J(%_uz1&BM5&Z(fsIP3o28UCFa&rN z*kDy}-d^7Km0$17?t@3ij&ZP#WgF?R@{ZSZV&^u%r7oFSN~x&Z1P<^kb;XLgwJ-aG zDA?!qb5s@wD*?r)rw95{^s!qy)rehM2A4(|`fZN zBRg(J>g#qGuo1WxC{Rkh9|(pY5w>@ddlfuEQ|84u(fdM${+UwBK5mP>1Q_IAmZqL4 z;-@7bPRPT+EkMzYN=Xus41oscu?=tg!UUVExs zzL$XGWaI{`=Ceo3`p?fU%jhwYruKkkld->Kc6RaAYn;~or#H64@5|w|1jO5~sM~jU z#if%IzsmU;j%7DAWsYUzb)saKZJ_;{zr>6;t*9wi`B4NkSm~gvl11J2iP6lF_aEw1 zKQxuzAdC}TY_)T>`*OMlb4{7kQ9oecqHcP5qF+Iie~ zd}wbXwssyk8|2TL02hxLCsuBpSh;)G_`iHDe|I1lzN6(2iWan>1uba7*~0$-e38f_ T< + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-terrain-movement.png b/files/opencs/editing-terrain-movement.png deleted file mode 100644 index 40777334bd2102cb5ca7af8e8acbb53d8d9144a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1745 zcmV;?1}^!DP)ZU)JZy0Y&#ho zRFY$*pha7-wia5)(c)BRm?{NQ1KcBH~i{TM7s z3P~Et$7Hj6&*>j~c3gKNrX@K1vCqu@vG=_9p67kv=RD`!3mIgPK?WIQkU<6)CbZUt zTI=xnoI{-_;$wjIz+AurF945;NXJOe<&7}$0_H6KOjOM?_ zIB`vg&(11PrB~;x(cv7$;cjC#ci5ViE?dK);U}VoyEuV0j(9Ebl!(OL*tcC$e~Vl8 z^jp5RWSqMCrm#m|C}BbUad@@M;47F>?9nv{E!~DIauw4G5)_nz(}RlU&cM9TX=@Cu zuOEiA)G#Z48<6kZK?64zR<0KVt;kyRqndDaA6#h*}0EwwETma4xzKG~l-HY*X=8;75tG*}B`vJ`clroo40f8Q-f+;cVnUk9!fkv?~= z4!9e*RYdlt%sC3!rIf0));w(_^+koeR#Y(Art+g${ zcAzNTewEgGldHyKz%wEeO_}7=z;@5`uCvzSD)1BF9T71zt91Q_Nq*Jzf)DhrfS83< zZw1T`d*p=_OWz4vUg@{jvA5o&CGiX}RYYdG|C51bp67iFz!xkC67kC*sVT_q(jQIjk4m_H6{K#^5DiISOwe<>=2P}ri~W^`+*}O z@{KC3{nG(e*@~YoDD=h^g%XVCjsJjr625}H9q@Qt@asa40$1HNz-AGNxkKPApi)Fm z4k5KAuBbJs#_~}%n(O3q8&E4E8@1M7b$0fkh&+{k&+KabZl7i1+6lg@C*`ca$vLTd~~D_bIFd(1&#nW0o5Y%$Mo1Ltt$sCd&l{T+ABT6 zIf=a?iI9TMLD>2)LrcGXE~L2G$>yvTPlx}%BoK*2Vv$Isp{%UzSzszq3cOKPR(3cN ziKHgFQbApo;z#WPO$WMdWwEE!Ww{FF&P6c;?+w78y3JsZh>mM*O^dmG}tz4FUIjoE=_hpH)(&b&(^!ZX}6+QXjUm nT~{*5AcG7t$RL9ZE=2qnRg&Tl5w(j<00000NkvXXu0mjfGE+`o diff --git a/files/opencs/editing-terrain-movement.svg b/files/opencs/editing-terrain-movement.svg new file mode 100644 index 0000000000..6c1417fdd0 --- /dev/null +++ b/files/opencs/editing-terrain-movement.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-terrain-shape.png b/files/opencs/editing-terrain-shape.png deleted file mode 100644 index a11bd95d54b2affd7f555875cc1e1a4f19cbf6bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1399 zcmV--1&I2IP)^@RA}Dqm}{_3RT#&AYrpQ~mJFsQQ%7$#<1&U+%8X1?ViD0K zUFd>|G`gEmI5IdLx7?;0B`L{=`lk_+GMYwAnxWVgrNop}hQ{UeuD(2HX1C`(ayiWT z@czH;y`Ht!v!3U_{?D^F6i`3`1r$&~0R>b|c<&o}@0*^@J=|Gxya7-G^Z^3!CNM$L z{>t7fs$|FOd++A~G4LJGOwt9w0br~5zSMhvewE9BMSL3YAg~wcAnBKsO(S5ZbFN_s zVJ1*q85wbvM3vrlfHlB4;CkR|U@TAuJO{J~RsmB04~zhoo+-8588|)}xEGi%X-sOo zC-5F{45(qRdjKmXg@oN?;4xq}F!^*2;!ZQ-y?_sZ%Ylo5eZV%L9dJbyMHzrxE_V=! zqA03o&j6nTJ8f9mlZ>SIzPs^s9`KB$GVgtLTq^0Xy_*RP25yzKHnC@IV1;w8YY1TmFe#Z4mq!jufQ`U-prdhqnQ8oB z;C4wX613QC>heUojR1Z#8EsPTXkvUW&;@8K=^uM{9q=yjGOz>~;+%UZgzyEh2xyd+ zU+2A_Wzm=b%$IaDu}LdnQ4~d2hY+v`ya?=)l=I%N1@=lBm~yVRQEw4Y7w87GDR(qs zHy>y%sk1#70i&ZRy3bT=4KPB|j>Mb@c+&38lE_kg+|qq4r98zz`&=&9BZSb-##aFc zz4z;ZmU*170jveymNZ(@NMJp%(R*JlX)9?MaGv)*_TC=?J_L5>a=A9ST<%)C)MHcp zuWr@;IPiNph3cA2-ewiGj#bpjCTB-ePHqSO0*Y+0k>$Mg-Y+%EbW6=y0hCGVZO^wD zJuk95)Iw78)SQelV>B=dIBb5-1*S+klD@=sfr-FKV6mh@sqx0Zr@$6Tci9HLj4EH6 z3bmBfC6BT>lhHqb+7{hOz-&p!tqJS~bd|Ix57mZRP^Tq@#Z@4qb&Nj!fl^7+z4v{s zz&t8xPM$eGIp;16A&iw&T*12Iopa+t2;WO8$}?VU{C)%|cY3_tyJ_j#ax#UioLU2% zb4^1Coh5yi$L4*zL&JeLfX2WLl73EPZk^F?m=!Vy+$iZlYQ1ZKmG&Jmy__m%+Dg>1 zNc00{0W%~8@BK}{Vx!xrg#8%nM4tlwwrrM2nwpZ)epYKXc<*bPOpmkXt~7o3Dx;Yz z&qyC&3ve^gQ_|`@wq2cbYeNVC9VKnbW7El0Z8^~0Xmi4dr;$081H^HBJdWd)*=+VD zpfzwAur-^_Zi?ghKU}w_?OE%QUd?8+b>lc*ld!32m$r*h<_<|8#c^DAE>FY zW9i=q+%0Lh*&QZt&CQ4Pc`Ca>yt7mzsn>IsK9O^FeslKR*An7707Qj>tatR zhgI#jN|IW&%<(Ce>G-+&u~oyeQa}L(6i`3`1r$&f@h_U)TWhmzf$#tT002ovPDHLk FV1klJek1?@ diff --git a/files/opencs/editing-terrain-shape.svg b/files/opencs/editing-terrain-shape.svg new file mode 100644 index 0000000000..8f16b10dbf --- /dev/null +++ b/files/opencs/editing-terrain-shape.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-terrain-texture.png b/files/opencs/editing-terrain-texture.png deleted file mode 100644 index 4a88353eeaac24d726a6a9f1e9f374e46149b095..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2100 zcmV-42+Q}0P)`5VVS=bX89oOH}v?XF=|PH1F?2`lX)Z6vh~ z*^PuEUkGYi5oG(Pi?fu(x=O5Lm)*o%D~Rk0SueU+P}GZ{3*A7o>844I)2P!ljx(O~ zf3g3We`dj6TDFDxygg@r=llGg=leXr-$0l!VZww76DG_jiwTJPHwuqTOiZ*jG&BV2 z>gv?Y%*;w4=95<)6B84eotQ7O_|? zkw8Tt5Gc;e%hN4ewhT#dnf=^ zRWTZkaR#sM--ENn=y1M>QAj0o06|dJ@QBY7&oRgEIi;j*CJ$vxr z0ga7~+`4rOK@jkGJgi>5ntAi)q0{LA7#JAf+_`hq*4CoYXb=Pev)N2RK>;Z#DWSD` zdV08c@uJe$*jTM7O7*Ezr<{K&0~U)V(rh+Y0f+T^eb%^f<0M5<5Jiy}FJ7>D^Jen% z^HEh5Ns>?$g-x3_F?a6VAtsh2iR9#DCQh8l#fuk_B#GA6R;*SlilQ)f>{vV=504)| zrnR+Ic=F`Q3`J4O%x1G}Hk)s@wY7QwPzK&-P5{5(Pj`1W8jXg^%1UBmV?(CgvSkY$ z9UXW)9wtnfKuJjn>FMbJJbwI``uchKtJM-8 zACFe6{Y~8OF#Ee1h|I{ysM2b+hdmxomQJUWf^`kxcDqSSOQWu?j?~mt;^X5n7!1_Z z)NuX!bqodrdc7XE+f7qb6Mny+`}gm&e*JoSdwYq9h(HuYo;`cUix)4LJ9jRhfBrd> zCQV}T;>C1!cJlDy!y#r@6h-Lk>zm>C`^(0UA1^<8^k|4jgtyFDv32X#VvEJ169j>~ zckgoO&>`;JxkF@RBmgT`tYFEKC5WOJTpXLt#)%UrFquq4c0y4UdV713Wf_e|Gi)yO z_V$vSo6Fj@YeVJ_1Ol|Qv`|%5#fT9jaJgKhq@<9Smq%J!8lot2L13+x-0V&-C~AQ&3QV#bOzz zsi&uhef#z)r%#{Wr4dE(_1(L72Qo7=hXgP%FuL&3CMG5ZyWP&Zb?fl^{g};W zGBY#TyLT^%iHXBL(ACw&&Ye4%Jb5wz-+%u-Yu2pci!Z)-_vk@=eLc^gKc}OkgM0Vx zq0wlFii$#2RcdQ%Q51!ooE!`W0|0}AgLHLuVYk}@f*^DUw?s}#N-Cc{d-kf*(o*A_ zH*ct^siC8zgHfYK4J!?&)5(Sn8+iKkDF+W8M3&`H-M)PJlFG_Tva_>?{O-(|Gi=_x znK5I=0O0j{@p`@F<>g^AnW(9$Va%8@!?NUZxrm90p}f4Dh=>S&`spWMU0t0OumQez zyO2C>+O+bhsHjyVM~*ZGyHAinAb{KLX6MeGq@|^W1Rp=#jn5q%J}i)>FetY2?u= zo;_RkdcEbTsi~`qii(U=r%nan$&)AS-o2YESFQ~4ipS%@WHPaF<3@Bk9hH@pbar-z z+HtUrPMn7;2^E7t?byb!*~1kZEHkCgzen9bH0y#oR*W4lFH}InX@W8 zJKI=aU;o~k4Q9sWa-r2~k!2aZUO(&|pU;ORNvvMInv*9_`hNN4mykLC?)!k8ot<4i za^%QWf*=eryQ-=<91c=bQ?XjD==FLkDk`{s{W@`RaYMSy|Bebir_*V@diClb68^C+ zG%`>QtP%vl==b}Hjg6(Ww3LjDjL`H|Rh27OuJGfJKcXlK8jS{3RegXJur)U~`}ilZ zSS+%|V)YO=q`YjgAcNU99|H)nIzyA7bIVC0Khr+@__syF( zRaI40MN!my_wK2Mg@yfziHY9-tqCQO(x eVZwy@MENh + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/editing-terrain-vertex-paint.png b/files/opencs/editing-terrain-vertex-paint.png deleted file mode 100644 index 2b3f0beac06ded34f85eec3c01b2e6ef37e1b1e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2545 zcmVrQ9s?yL|X0Rk9VgeW^f_AM;I z1yLeQjSlKK1ENv|*2prH%8m{&E{Kk!BghCO1QDEoVW)x$2!xOzBqW`5x|2?KI{mi$ z?tB==DzTZTA@o^8+$hS|D{KKUh_E(zRb z14C_N#yZwL(ADwy@Ju&6%!Ug=1BN0a22A!7tJO$sk9UgpBE@0HjC+TG5a7_U6R;$4y4NQo6aGJ1ca!>o* z@ja7~5bFXQ?ZE9R>_TloIM=9R$jH%1NlC$>g9qX9c#xNuhxqt-06-`dLTPC!Y&IKm zb8{o!#zyJn;Zk%kWb{l)hUTdQtF1+Y$A`VO4cHe7t=bFrGWLAZ^b>!h<7{Kcci%Yg zK50j?Q_2wtpxY8a6Eid~kJVpzV<$UX-zJV4Ga5~Pe|XE5Eh-_zJbU(RSgqD?qgX5! zWMyTcyu2LSwr$hgE|=y^Of;QuY(nwT6G-dSMQ`DvQRQt?8p3U+G_y2#80bRYIV0g( zR>lcbRLMW`3rvccHifL6+{g0R(Tkl9H0PIdkSnIXO850II60uw}~@WMpJu(4axsQ&fcMQ>MaZHpA=j zprW!8rALqAk-G<=vq53hH9)WT!W$0aK%@l+b$R`H!k5<+?rHij1}5A4+{;%b+sG43 zr&`g!8v)FAa2g9@PeN5aRo*JPZ2A4K^O4fAGax#^xJ6m>>Km5FfAhq*B@+w=@$S3t z!Y~Zj?RMz8j{EPwA1;>*0MOjrj8|TI2`@hTT*Rz139W6YsjS4--8;imGqZ$tvTjwp zK9FDtTBsGLiHg^a2q@?4_6x=`>?#i2WMHyAZ7^T`!ccMU#J+$<)KTL%VgDIT9@xWT z&CYc2vMV6|h=ltl4uIrL2h2%eVjN$&;Y~7O(lqq$-TOORq3b$k&U^@&nVqp>#R>?* zcdNCwwiespe;-fHoW+>GNnnkB$aNlhf7vOk5NbtNo$#;10plcBg>DQO%*`+niUWd* zeN5eONHCUQS8;%hnUJ?(Vz&6Z{Cj}*ZVli7=NyC*fB+vK6Y$nij*cz?Ge&kmuim}n z_|$G@65olw{^oYvJunx2`}X~w_{EDBVbaum5JGUY^bi&1DCAT-Y+AG$*E#|V!G*O@r*HS-n0>e zhL1wdfB^u2hDJYDuUbi;eeQW3fcWpVxxj)Q1XGGDW@rxeNBut*j=pLbcpk$JBMRwza;Y9ym2P<=|TZ928Iv|YD+`} z34HwrXUNbIBaky-2pDz1Q}07jVIk}`8{94zukp2z2MT@#UzkBR07?LhKoD!8I8e9) zRF8n^I?69qV&;s8(Xm}R5DKB>(|tI1rVIt$dm2(8L=~?O2!^1yhK(wL(M?(_>cAJB zqQ+Y6E>`}-Jy4;0uT~s5b3SWjVIwnpuCsxuafBqtQ(V3t(O3NCqA+>FJ;=!F0)HDv zNF^}GB%t5GVff;-!rR9V>JC*S&WK7X+Jg8K3#wZU`45K)n^0R+{5dXNW5uTM3V+iM~CdYpsG4_BBHwD z0zCWQQey|rFjGN5%b~42b>h>cLzgU2&R`Y=)K^`=wS8OAb$EecGsRL?TLV^o1{ba# zk(>0$5>UGSK+zuMmNs+kNl&TU5a)77`%VvvopRbos;gigw*WDT$)JV~1AxK+m6#Fs z`yqWi7X}v)$y;SOQaeyHS75nOw1OZa=x>Bt@;WTe3}{ylDs90l%PQr|Yp#|yD7Uqp zo;WnyMs4mF4fkEohwmytTS^u;i2^Y=hsHP@m4^-6#X{=u_Y@4#0{6-Fn%r$131y8C zMG8<#p=&Cn#!A>vY*uK?#U)1Vk#)siL-|<;n*8pN^t8<7ddigf!Cntj#bh&F<%LwY zr=hyX6v)&B0N@5=S=C1blcx;cJ0E~W3Bh@Kvm*M6mkD3gulYFmFY| zb&q)_kUf)LPr3udFp%oI1ou~eR>Z*3r9>%TyX){z7k+zQC2lai7bHC?bGe$FzknJl z#D + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/enchantment.png b/files/opencs/enchantment.png deleted file mode 100644 index 9bd54b8f0a97d6bad0ca2b8303eb8771339fffed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 422 zcmV;X0a^ZuP)`EUcf$;B8Z*% z>};*X&LYVLY(x+QE6*WVi6-lB!X|G1@DyAKJNtbzv$LBGf`1O<>*F}yV|{ImnX7KC zeP%i=%koL>zJnJpXUM~T=g*bO6BCxZiW|*&po$;l5e;R(ODZ%I=4N0V$4Qu}T8x2XR6gQXh Q_W%F@07*qoM6N<$g5>ME`~Uy| diff --git a/files/opencs/enchantment.svg b/files/opencs/enchantment.svg new file mode 100644 index 0000000000..02849172f3 --- /dev/null +++ b/files/opencs/enchantment.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + diff --git a/files/opencs/error-log.png b/files/opencs/error-log.png deleted file mode 100644 index df698d14531014feb9f4bd4b8f6cb7e0a6e79fe1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 518 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=d3- zBeIx*fm;ZK886+f`vVk|Dshb{3C>R|DNig)We7;j%q!9Ja}7}_GuAWJGc|_pf zf~Tj8V~B-+?_^)^!wv#%!mLx9az&?fami_EaEbY^tq9Ps@(eq|wP?W)Mx~~T&#%=v zUY(sRp0D)6-e}b$AYYtbYGHswzt4=u5fR32!1AmELWs z{eA3L=(WljtVdH$X{xm^V->5+IK=Sgkn)^^oHhsN-3>n1+t6rjvR{>XPL~|}@x%6T`X{OjY>=$&eym3zu zI(3&ND0F-5Ukg1WQH_M2m)$Dg|8G&?`1QNr_-kbx_wQRR0lw$cZUBRx!PC{xWt~$( F697;y!(jjb diff --git a/files/opencs/error-log.svg b/files/opencs/error-log.svg new file mode 100644 index 0000000000..1feb39dfe6 --- /dev/null +++ b/files/opencs/error-log.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/faction.png b/files/opencs/faction.png deleted file mode 100644 index b58756bdc0c2e5edd09b119ba45125e99865a94b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 389 zcmV;00eb$4P)Xe;-rdM9~DL6@;ujZ9};_xF|BVh(9Rstehb2mk|dd9PotQ3O$H6;bE<^S(pqsuru;sjPir(dKbhltOh0n0V%k|NOy|KU}wdjgE ze**TV!H=I>vAwR>vMT3L}+_PTX2bpU + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/filter.png b/files/opencs/filter.png deleted file mode 100644 index 55f442377c439062fa99b406f9c858c12f2b0848..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 299 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Gs& zi(`m|VDgvGpMRfcVCm`UQRvF}@ZUZ{nwfjb!JV0xm&v=lX4Cre^=m3y)US5V6M77f z_#GJYl6Vh$9375~ z+{F*273}4oq>CK=VX@&0V^H%Mx9iOhnMzn&el*`Fj(dzS2^3eyc`WZZw~+f diff --git a/files/opencs/filter.svg b/files/opencs/filter.svg new file mode 100644 index 0000000000..12ae65d398 --- /dev/null +++ b/files/opencs/filter.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/global-variable.png b/files/opencs/global-variable.png deleted file mode 100644 index e1642ac355598da6a2809471b43133e22cbe54a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1FbW zi(`m|U~+;4YtIYE2d`hBW)g`$d*q17hxhmQ^E#C233M}mh72z!W5$YXnymt!?!BWKf*D_5_+H!?OhU$ttL9edHzhHcXx1a$|?WW4rO_}3N8 z7|_HyT_s>KYrs2o1DzcWIjJ$PHb^mCS-^GW0(;3eIRl0F%r_@@m@%x7OnD)ru&piY z6iZvHct8y6Y()({2CZ2fAzvgr@)-?vx?&sFF}&Q%F{PZvuz+a+Q^6XB%pC^}4Cl;h c+sDEXyxrti4!`L+puZSAUHx3vIVCg!0DN0y;s5{u diff --git a/files/opencs/global-variable.svg b/files/opencs/global-variable.svg new file mode 100644 index 0000000000..00221d24c8 --- /dev/null +++ b/files/opencs/global-variable.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/gmst.png b/files/opencs/gmst.png deleted file mode 100644 index 2605fb4b963e630449816116c7797b3f33375514..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 338 zcmV-Y0j>UtP)f3SO9Tl1deS-Kw<-~48kGd zy%I-u3JR9M@$bE7*LARA7!tj6j;?7bE$tnAzcdR#b5b?P%tqx_N@9#2sv?*U^E}@| z2z&4SeH_Ok)c_D|nx;}@3?{lOemi=AGA`!JOFh7pL}E@Kk$d8OuJ7~}0CJriiU5c& za1ZzsV{NMygt=zIaL|MDHURbhY^rlmW^QBvYIYoK3nedMqtD6;hymc<++PpicpkaL z!`3zS833Y-3Kjv_V`%wq^-Wl(834U+X0#vuGh_Y@0NKgJRemv=X$iD!$+f)96a~WG k8f1N?5c`_T{{tdDZx42HY+xwFFaQ7m07*qoM6N<$f|hQAAOHXW diff --git a/files/opencs/gmst.svg b/files/opencs/gmst.svg new file mode 100644 index 0000000000..2d96e4cc39 --- /dev/null +++ b/files/opencs/gmst.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/info.png b/files/opencs/info.png deleted file mode 100644 index d7bdad6cb1699891de65fecd17cd3708daa198bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1234 zcmV;@1TFiCP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf5&!@T5&_cPe*6Fc02*{fSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@H7+qQF!XYv000C*NklHfC5|6kQxOCK;pF6$>yOUvN{{oXKg_O7%ev#Js}93B zx(csOlVdV55i?Q5)K$m3?{^#CuJqxy*#3C`_dL(*_4#~1pZjhE0P!S`t5jh^eD*-N zcFV7PMzbwE)nt`x^oKq9hWoP~!P$$lB*XCPgHzb~6<|0rjV;}Px@`!u#FvkGBjJ&N zJR$GRmHRHhcpTtb{sw9I6M;>3JoolrP!=435SNwWsf1?&8vS7DQ6Q|%2`BY-khZB6I$mXk<%-G=2IHYDvGLE@efXxh&qe)l=VwOOIwWkpO)FQm!lPH!ac z+X33z*r@8`FnlUAG`_{R6U=n}}?_frzFX2;XrXLenH7^asJ-@l--g zAoYp5Qx5pdw3E$~AeQSO<_QqfRgfyW4VAxv7{|fA^cfRKol}8DwS$hz*`ABR$)-!- z^q=ti*VJc2B= z{Vq6pd*+wTwF| zXK+$93~tQ_{u2m)^&5nk>H4|gq;SZ;0@AV*2&o=(RLgW!eVWg5bca2Dy?I-%SrSHiG1k`J&$cW3uW^)C>kdqZ1kSXtwbQG#ZmL&sNh^+4%TM8^4Su wuEH6$(Z + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/ingredient.png b/files/opencs/ingredient.png deleted file mode 100644 index f31e6f581301293160756054ba1c1f3a11eb40e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 490 zcmVoHmlChIkX9kMIER zh5Vc(=5jexHXVStNVgMVKf=ePP&xfBw*RHPVF1lr@DGGTB3J|O0DG_1bHr2Sbps$> zft4YOU<_=SB#*CF5%Zkq=_`u|K-{HH{}4}rt$@uEW8*DkIKsIAGXU`b7q-Tkdn^lV zoO}y$#$t-#dBi2~P_^B`9^wH9NZ?!(0`R)~ z5JyK~SCR53{6S(3B_G5WIP)_79q@JP1~dNxpQV`q*$@=j(JRFbR_x{hyVZp07*qoM6N<$g8u2!4FCWD diff --git a/files/opencs/ingredient.svg b/files/opencs/ingredient.svg new file mode 100644 index 0000000000..af2f736e64 --- /dev/null +++ b/files/opencs/ingredient.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/instance.png b/files/opencs/instance.png deleted file mode 100644 index ce63e64ed66830118ed9c25e6b817be8d9f96909..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 618 zcmV-w0+s!VP)UGq2zJSnbXnj}@gMNyS!ybGM&f17qqb7rHS+D`Y8@40%+--0DA>#p^QU(d|223)fMBN$mC(w)k z4GvZmAEh}@Fb1OF2(ejk0HnZEnjQQCEEsFc_-E0<*y$JX(e8;4?*ff34gF>F=DUc>^9NrYBwnAHePvXegqyAk9fnD9R{FTuppn z(}j)$y~ah`(K!Qu0Ven-O30eiwe|+ZC-5(zmp~tw;HqP6`xZSy{wv%*KmnSxw(ghb z&ob;9JKJbI*%VEW;5KX9U~h&&@A0+jJdMG+d-I|G3k+|{pCe3}VE_OC07*qoM6N<$ Ef&wfNegFUf diff --git a/files/opencs/instance.svg b/files/opencs/instance.svg new file mode 100644 index 0000000000..fe344c905d --- /dev/null +++ b/files/opencs/instance.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/journal-topic-infos.png b/files/opencs/journal-topic-infos.png deleted file mode 100644 index 4cc4464897026d9042d46c8051d636549f86aeaf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 300 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1G6o zi(`m|U~e!d-(dxw>>D3)7W6&Vn4;wLa9&W>tfPtd zS8aS>ExmC{;Ki6+sTUlIIa7A@Z(Q>Ai-xn8XVJO*C7dN*)-_Y+Dl}VKy9#dj+~BbL z;ln#ux2jp~DTrR4{PK;~`xZ|=12#b$%VS?70$EaSI~zV_I4Qku!3L-AU7Vlv*?X)f zM?uqfcy)1SS8^OY3XRXXoXXn!62%etxQq4ojuyVy<4PH%>7;_I}2_J=z_9{}X#o%~lUn1NxM~)78&qol`;+01)we2mk;8 diff --git a/files/opencs/journal-topic-infos.svg b/files/opencs/journal-topic-infos.svg new file mode 100644 index 0000000000..4351d0668b --- /dev/null +++ b/files/opencs/journal-topic-infos.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/journal-topics.png b/files/opencs/journal-topics.png deleted file mode 100644 index d4e58a288503ea273e85169fceede9099d0c32e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 273 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Gy) zi(`m|;N8iu`3@=Yw1%72T33WWczdPcd4d<44|9F)rYTPP@sSgGAI&*^Mak)FXpvst zSD9~dJlVO`L9xeLe`N(8=1o51*8AR^>C@LK?mdA5KMvfKeSNx2#J0si#A1rV(j#Uq z=X`D*zP$ea;=2L`%^h#v_ncaiKD$nfmvPmb0yBjjYnC~t-xc`47v}o^5O3oj?knC- zF$Y=g1XUw~^-szROsOlZTz~Ue>4Go1Q#w~4JCgmx;blVQdwb#THHTlE6WYsoSGw$B TaB+Gs&^rvCu6{1-oD!M + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/land-heightmap.png b/files/opencs/land-heightmap.png deleted file mode 100644 index 5b460a0024e053cf0a97a0f720ad087d98025c2e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP_Wq3 z#WBP}@a-f=z6Jvhmd1VS|7^W)ypcn~Vn&AW9?9xRzBgir81~d;)TK@H>gzE1{`H=% zLuvPdP}zBN94?nBWv!am*ijIE-g0kw&%=K)ib1-Gp~r8DD2IQIe`p=fkrDD*L)ydt mN8HA3_go_D?Plsds9@O2wP?>w9q|gF^$eb_elF{r5}E*R0Yd`- diff --git a/files/opencs/land-heightmap.svg b/files/opencs/land-heightmap.svg new file mode 100644 index 0000000000..701b246640 --- /dev/null +++ b/files/opencs/land-heightmap.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/land-texture.png b/files/opencs/land-texture.png deleted file mode 100644 index a96c4bf8c7b8c1334857e79e33ddb9230423c3f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Ftc zi(`m|;M%~8LWdPNR_}iI`OWcH&r74-SDXxte!pw?M+G587SGe0awbQ5alYG@JA1mX zFlSzPo6D7RJ=IDj3$!ZARN}k}Pbx789-Q;}_96Ep1^pIDHyRV#|Jd)DVYpY*@*z9Z z>kk{3q!`WLdQF^*H~Q?^T%O~PcO>N)|BBJcn|pQ1;lGjnFA7qX=j>Es5U~5GBK^Kl z=7>S>hIT90J15KLXuEC`3eB26@s0K$hY!|Yvxp`=!nrE9M=(02@;u6{1-oD!M + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/levelled-creature.png b/files/opencs/levelled-creature.png deleted file mode 100644 index e6cb1f54c7235440dae1d180e59054655ddb11be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 521 zcmV+k0`~ohP)UtAI8s#7eXfu@Ee@-ED*vf}K`c z2x4KQf|Z~)NuhQIkCaKa^#D@_1-n>iWuc9NM-g#%{pOjsY(NBcV3_0o=Xh`58pc1x z)b)U(3sdCaRJc_U4Vav?#JS7;>%ckLcAxj+Q4<)uv{*iB=c-A z6^3B~tOI=(64K2^x8Mihli*3qvR+E1l4*V6YDF+rT!rkDuh_O-hgioyrr5^zHu%xe zRRcQS19;e0iiaSd6Q_OO@02O{DRdWX5RTO3xTh-CkhO=gM`)30^8iWwKJ<}-Yph(L zklRofP7Tf>{x~`z4JtTA$eY+E0h4K`9Bq`z18}Z10c;xINdS%jj%h$FU<)RV7Qp+# zQxrc47Lx-0j=BRJxu8fO*U=gYDPI1j*dX^xbIXuV7*+n#ky^O})qrk>s1`0twhJW3 z7IM@NjT1{wsBiLMX3#8&b{^BaWVJ9sxvcBUg@Q-h5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/levelled-item.png b/files/opencs/levelled-item.png deleted file mode 100644 index 3c819c56d0da9b019a056fa2088284d5231f13dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 516 zcmV+f0{i`mP)f*^`uVPz7?FYptr zQrOs8Sz4qJQ9(@O4@j=Cvk=kFP82L`BtZ*BNuzLgKC|vD8(kRY<9*+m*;_7~`E!cJ zVw%lnGh+Tp)6}H+TogsIVmS>wpFmn_Hk;>OEkol9dEFNa-aW=DN~Mw+r(4K$iIXJh zp~1JM8TW4kCY=bv23^gmi|_zj7hF%Efp|!DCap4Y!RC2tdCQr`uaQSKH_lA~7!88p zfi(wM^OgKQ48t*5pV|}bajcJ8D~{tiI9Jla-{1)1mK9%u4MFU0G#WCbfbD~|scjP1 zFmv2DVD)*XE!z4v{tdyq&F}EDHa^22+BoAHNCAQQ!uT7>xrS_bLGvk^Pf87}!z)Q> zPFDjhdj1yi1mXh17q9L@_YELDGjYZtzk!H%d@-D1YHj8D8=O+yz^)ZU=w3-{_$qd+ zx!+)qXZ!$IQIKiozhGiWvqb7#sY>3f9Crl>pFHC^qFn_cn^47IHN;Kwj^k*cUj75l zAS8EB!4QwI;Uh!xU|-7juV8A3D-c`da#=afBVP8!0{j5|jM1noWfZ3X0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/light.png b/files/opencs/light.png deleted file mode 100644 index 55d03bcd127699d3790a7161a24fea83a25011e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384 zcmV-`0e}99P)1R5%f>k}+xmQ4ogTY64=B!XkZ0v3Q7xbzpZ%Z)%gmGw3Y> zDHE{YUBS}U93agWAq0;Qq9E-0&EkN_?4po?#|$&`|MS0jujwi*2!f68`@dP1Eh}2e zbfcztL2U%lLrsq}2>?9;+NNoGi=UoF0Jwp@0qP2nxJ6FXnC4ko056{BJxQ%7iXlo~ zt-gQBnF@p%6%$m9B}PRP6}iMG8Q?MJQ<}ptEb2h!a#-9X&+~R2P-`Xuk{y!Q+N2&; zec}ZJ90OLwJ@X$Z;MlX11-W({$FjR;&hjh*l+7XeL&+ryXT;Ygfm}iDFa~s=B#BJ# zK=MxQr+h};G>r0OPQ8$5tX^~M;Dh+CT;hJp4ea02P-RT*QJc|sr9JuzA&IXjiYh;r eUIjl;u=pK + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/lighting-lamp.png b/files/opencs/lighting-lamp.png deleted file mode 100644 index c86517aa5743089b95ced316e29c48c1ffd2d940..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 953 zcmV;q14jIbP)WFU8GbZ8()Nlj2>E@cM*00SLKL_t(&-tCx8h?Z3p z$A5Qb>4f^GlEI*$PKw18Du@P(REvR04T^A)ZE7L4sR&(*l8SCLiqM5p`B9)2ilWO% zON`1^EgYK=g>pf5bo0;~j0bTlNS)LH8HS!jHuwyC?-{+h5EQF~&|0(F}@7|)KXl|~TQQe*sT^+Ju| zw=zSkxTJQdJF~f6^)|j$9f*&gjh(-k?e-Vy%j(nWVzpEKCY~8Y=c#A1gFU;R1m4JY zuT$NV8UK#@f_g-~7NPUCx>kKvy&G${2FWTm)|0@AZ0DZMyzzM$-#1G798_Oa4~E>O z&R2g9dGB~^X&Q&|%~7PPS*Hz{5%Lk>ZQ#jKRHp%N0{a1hxxjP4rFh<67XrYPsIUON zKs9!616{!KF?82NI=vZk3osnxHg!fl2s8oB*|=)JNbGF$7`pdECuMcWOn;;v1csuw z0eCEmR5e8A0zZzSdlp!ndGH3%8slN$-h>lKDLu@LUI7dQp#;1I90cx+qIwnh6j&E> zcj&DO`9ezRZaq9ebd>`_vte5;hZksedWLr#*Tifrr3F;5hJIN~!<9@>^v}wN*$d4VS-I1qB5K1qB5K b|1SOl59>{tmAIp600000NkvXXu0mjfg0iP7 diff --git a/files/opencs/lighting-lamp.svg b/files/opencs/lighting-lamp.svg new file mode 100644 index 0000000000..5d34d68817 --- /dev/null +++ b/files/opencs/lighting-lamp.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + diff --git a/files/opencs/lighting-moon.png b/files/opencs/lighting-moon.png deleted file mode 100644 index 36a6e9b5b1e449bda6101995cfb62a1e67ff1c93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1590 zcmV-62Fdw}P)WFU8GbZ8()Nlj2>E@cM*00o*!L_t(&-tAazOjB1F ze$P3#^tQEdTRsHC21C@yoTE<0N9J_kYDArxx?nQMHr-}|h9zuf(TK^WnHrtZWZBH3 z$u?!OWy>@J6Jn$UmkJC8Ma4}%CVmhRrmZWq_x7H%A1xEH1=?QBO#1xkpPqA`=e^H4 z?*~8_VT2Jz7-2lYAPVk0u2I+ynsX=e(ganGynnwGf&ycb+aM&%A@@`Sl(;4D7c1o2ttX z-10#l0RUOLwlu~VZA=5e+}-}Z)?W|p?F9fiB<}`8J}oO(yI{%kUA%^0KNLhslumYB zzI578afT2u>7)$ipj(H>HGPVXP!9|^k2hMX>j1z#A}6tl5P%l0eqZoqAFq!mkFlOL zTPpu)wp5-10L##q85ViGbZu!|bZpGb&MR%_P9HmLAFq&cTS%vm)FA{qU)y3rrYEP( zi5SN=)Eja4F!E^G+S18MDM@vL5Lq+JWcoV8yi%+99nk2s285C3k5 z{P2@k6l|0fO=Q&RbpGNugp2_uINE45d|$Y6Tc{$i;&%W5U`lG*9l>BMz!*pRJsD#y zuU6+zGi9A?JyUxVALc&lai&;@^7eA z5i4X_ZfdEoUuLbUuuX&j002_7al2U%^hX~qtNn@a9K#p`N?9+Zw0p=_7^7GKq$dQ& z03eWMxv%Hut=yl#+<9rd)(f@{=L$di)NC*a<(Ob$ED^p04e#bWr8RTdZMJv6-L>VX zVFliV4Yue7tEFrDob2u(18g_z!=9P zgt4c2LE!m)K-kmJ07#;A%69L5aa&9C-sLfjNGJw20`MAv`hnrlxC oddX6yCUaqg5k?qcgz-e>waqbso&W#<07*qoM6N<$g1ZCWz5oCK diff --git a/files/opencs/lighting-moon.svg b/files/opencs/lighting-moon.svg new file mode 100644 index 0000000000..0441eb3bd5 --- /dev/null +++ b/files/opencs/lighting-moon.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + diff --git a/files/opencs/lighting-sun.png b/files/opencs/lighting-sun.png deleted file mode 100644 index a54d0ab12498af2b0f3117d17f3ff8738d46f9d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1804 zcmV+n2lM!eP)WFU8GbZ8()Nlj2>E@cM*00wYLL_t(&-tC!fj9gV2 z$A9PCdD|WK%*^f#&;eWHmODi&TC!+gBq1mXvGI-K7paAU))EtA_@YKsQvDzXKJ<%; zEoUkp7T7<|9QEvh(#=75sSD4h%klgV)||{lZfO|WxL~H=#ZgZet_?b=%@cGqumEE zqdyNzPb56w%CE_42gh&#o#;z(4XeWOIpq1^8&2hxE zel655BrgXCqAdS+XsfCEJI&PK{YDqnvkP^-F3LUC z;fGBv=rdEL9~fOszO9RVA@n3xcIi*r$qL`w@l?#*uSB2${IemVo&1f!9hk}WP;PG( z%R$Jzg=pKWY}ae?>l^RiW9?R4IaX>0*gMI?T$?4jejn7R)t zzmhWfaT)7@csD{=&=@1Ao}y7ZaSYM5P1&V=dVns(HQT`#5ZSH@6Gtw4E1{jdY$hz| zV*3vf*E}9ceJv)EHz5;Qgq3KW3@|lKQ0~Vs_j-uzL&`4g(@x%6C~bz)3+ZBNBO=}# z?c5l%I#+?PfZ#mBGeMPL_z-@jcNCG`qU_RhSG<|iPX0!Wdnl6LvMiFj9fUl$hRslC zf?CUn-=9V-S*L9Gz0fPMcJh&FPikgx`khjCER)Hb=6LxT^-abR=~Y;hpIX+am%aq_ zP19pdv);^BI~Ht!9^eto`}bi2(|W|3E^-(W+f-r7!%VK1@mmpzt_xhA5As3A)0lXH zhJU&T=+jO}qNS%p zfOim)fBlkKXhxeLr*pSAlJ%WgL&LfN@!PGGIGrI}ur|(gAY2SSGToMb- zV1pS#tyfL(?-N1g_$^j;9mwd1(kP~mDfbij{R4>nq510xX(Cg$JBH}|rC;u?G)n(~ zz@I~`1k)g>oWdV_WeU-UbZ0dEU`nP}A!4bPAR)Uj^gWA zrZAPqRiXOW6q<+cnzBcS3Lv zX5v=xSAz5)RzgHxhS(uxyF;z!Z%;h+)o&TmMyL+)q6_i3VK-q3xJNbt&lQ~EjRbXiDX2%aefE5Q!>_cYk=d_dm8^K7MS}~`*k8-GY zgXWOF5Y#VR3}Ni5+D;>)Qy3qDDv**3UAtV-?X + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/list-added.png b/files/opencs/list-added.png deleted file mode 100644 index 4da26598392003d3c6299dce9754157a4110db91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP%y&N z#WBP}FnP+{pEDbU4!CrtO^9=1`@_D9F>y}A1V#(fLn>{Jj5-GU88T}cTYdWkmrQ?T z;84bR!$n2BafZR{jVBwY2EA!YOkgNdSkx*%qucmHLYF2VGsFF*Z0C&UbS(wi!QkoY K=d#Wzp$P!;nKK>$ diff --git a/files/opencs/list-added.svg b/files/opencs/list-added.svg new file mode 100644 index 0000000000..ecfa8595e5 --- /dev/null +++ b/files/opencs/list-added.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/list-base.png b/files/opencs/list-base.png deleted file mode 100644 index 336d4c59c1c75d3833f70a8bcd003bdae158519f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 184 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP%z8W z#WBP}F!{^Zuc-@I3<4KPE}71Hvf;l+PST7X1=ka74L=eb1)3QeqTBKqJmh19+7&gX zB^Wa-;dd3B0mnGe(4V`9BK774`a&*;x%xM+6R$JwkwWp*N4 cz#KjXiEZrSZVbYXKwB9+UHx3vIVCg!07UFG%>V!Z diff --git a/files/opencs/list-base.svg b/files/opencs/list-base.svg new file mode 100644 index 0000000000..a050b15629 --- /dev/null +++ b/files/opencs/list-base.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/list-modified.png b/files/opencs/list-modified.png deleted file mode 100644 index 8b269a31d3c79efccadfdc2c816f49a8b85ca9a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 457 zcmV;)0XF`LP)GAh8oHtOUiMO0X1s z0l~&r!GML0ub?(!6Ks^klol$I7f4}gGV6D*?lQXsiG>%=&iy&}%$?aW=0B}>yKF)9 z+a5u&NQ30Iz#fQ!1<&9-T)L{U<}4>b^BC1@Te217661P_BX%~jk%8$xU4wWPp?0bw z>S{C(;nbxWVu$7x;$blAx~@vDMQxV&=7)=GjDNmu2rO=}DC7HcN7Dke2RJjV z2c(arludxYAqniT(5y*vA2_>mG;B?ie0+g8>B^DKeOm0ZHaVIoOfiw(pD&PJsH*g7 z_ERZ0H^EyFs$Rus7W{b|2Gab(k@PJ71U30p*fHactLJ^ljS!w0tE9`cl#K$RM6$^( zm$~IO{tkEqU9bpFsZCRBu=^abS|%V7A`>j~kjjPu`TsrQ#Cu3};!OnJS=Dj^l0ifs z)IL}TlR)0eE4TnBG|NPT8Db&uAKPG!e_?$A%cF++611oh00000NkvXXu0mjfI6TI4 diff --git a/files/opencs/list-modified.svg b/files/opencs/list-modified.svg new file mode 100644 index 0000000000..6efcab4cd4 --- /dev/null +++ b/files/opencs/list-modified.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/list-removed.png b/files/opencs/list-removed.png deleted file mode 100644 index 618a202bb9c7503e8aa98d8157bb57c3db3657da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP|(8D z#WBP}FnP~Ap(75Q35#5eJ>IWi4B=oF+Y<4k|Jd=PsV$xXGTblp8fNosZWQp{$nD0v cz{!b;Az%~Jq!PoTQlJ?Op00i_>zopr0P%Ap*Z=?k diff --git a/files/opencs/list-removed.svg b/files/opencs/list-removed.svg new file mode 100644 index 0000000000..157d4ab5e1 --- /dev/null +++ b/files/opencs/list-removed.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/lockpick.png b/files/opencs/lockpick.png deleted file mode 100644 index 7b1865f50457b6a0343f95f2c686cf376a1e70f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 317 zcmV-D0mA-?P)%d$vB zro97xu#ew?*80t}Sj&Jzpa`G2HJ}UQ7|+fC8)oaa8QidDkDYktEuO2kxic}#)`6}T zi8vToj^{-a^WB7BroxWC>$*-+6iJ$<9GbC=9k5;C1bBlTPexh)`4>QpE1KYzt%1*7 znD1HvzyWX!yaK-1a4UcqS2M=2mcZ9O_!|Hm0`Gu1<$GVub=5r>iy80%Qd4^5D_tZ$ P00000NkvXXu0mjf6UBX9 diff --git a/files/opencs/lockpick.svg b/files/opencs/lockpick.svg new file mode 100644 index 0000000000..b7e8ca1c3d --- /dev/null +++ b/files/opencs/lockpick.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/magic-effect.png b/files/opencs/magic-effect.png deleted file mode 100644 index 44b682bf4ff359ce3c8076379cf27ccffa46f673..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 356 zcmV-q0h|7bP)lRa+2KoEs@SH42TG5Cp;C=F>5lI=2gq;(PM(4>e2 z=SgAnJ;T6yy^bg{lD#|g-u(CtcsF5XS#ClIpMu-A{b-tIH$lyZ>$>g?sj8}@pt*xH z47|wcx?8^U-a96U(EGKxU!9}NFa9g!BL&`U3O->g9{4lI9{fqZY)}CIg>FZPOGw}N zUL@Hy`G@O!AGp451Pel(Q{==1D@~$!l)NT(Fs{x5aHKY|71*9DYSVc~k#sw%k+)QBlC#AB{V&8Va@&b + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-close.png b/files/opencs/menu-close.png deleted file mode 100644 index 81bc986775089a2ab8b8113096c890a47785e84f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 438 zcmV;n0ZIOeP)_eP4v08QYouW`+TlKHqY~h g-$r@ga{kZ#4H#~PogGl%VgLXD07*qoM6N<$f?Wu@<^TWy diff --git a/files/opencs/menu-close.svg b/files/opencs/menu-close.svg new file mode 100644 index 0000000000..6bd1e9f275 --- /dev/null +++ b/files/opencs/menu-close.svg @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-exit.png b/files/opencs/menu-exit.png deleted file mode 100644 index f583536fb695627bb749c8e742df32f37e845995..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 257 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;j%S zi(`m|;L)Iif`<%vyo1~x&RF8ZkbH=3ZcCYHl8VLdecLiWy^yWD(y6Q2cS+!%Y#@8Z zSM!a%V%(O;BsO`JOV0bbL}HSw`BWdBlPzbN0#X;7xgOJ;J>z9nzN|tI-)sxE$?|lw*K7*&LpUXO@geCy5 CkYic^ diff --git a/files/opencs/menu-exit.svg b/files/opencs/menu-exit.svg new file mode 100644 index 0000000000..e1256eab72 --- /dev/null +++ b/files/opencs/menu-exit.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-merge.png b/files/opencs/menu-merge.png deleted file mode 100644 index c76288ae168580eb28605cca6c82876f8a467639..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 219 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP_Wn2 z#WBP}@Mw@DUxNY<^VjMR;fMaatbV!a%-QCPCsLE&TJ4%}>Aw1B#;w;HZPGQD8ukf2 zx>Fs0vQ(K>N8;+q*)wc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-new-addon.png b/files/opencs/menu-new-addon.png deleted file mode 100644 index df137b2b204cb94cddfe6bc5bc329166f3bd0c23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1FbW zi(`m|;L=IFe1{ZxS{o~EAE>$cFN$M5XTG67GU&z$gXm3P*ne%c33&Br`ObTm(;sHX z2B&I1pA?od-|(3E`}>h>Z^Seb*T(MsKJ(4qc=OsMf!R|g86B&sI-#?mfc442-en9I zTI(~MChy4*O3V4?((D$wvpkC_sD5|Xw)DRo5Obi@&TU?oclyq6(>vU+-;`lY>2dcjb9C=}__=+$ cM*mmV)hqqBP323C1p14?)78&qol`;+0L{H`dH?_b diff --git a/files/opencs/menu-new-addon.svg b/files/opencs/menu-new-addon.svg new file mode 100644 index 0000000000..46aaa1e1ec --- /dev/null +++ b/files/opencs/menu-new-addon.svg @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-new-game.png b/files/opencs/menu-new-game.png deleted file mode 100644 index 701daf34b179e4c2e507788fd38366f9d83c9c07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;iE) zi(`m|;M?Fpz6J#j*K1#|%-&+5~5+}}`+Zp=FKa9hVk!j1d-CNBj$lkWtcHpe!-tP?^(cYK51uD5Gc}kTEZqVBl zDLgA>p=XQoO?FjSQZyelF{r5}E+txm0xk diff --git a/files/opencs/menu-new-game.svg b/files/opencs/menu-new-game.svg new file mode 100644 index 0000000000..416a774d30 --- /dev/null +++ b/files/opencs/menu-new-game.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-new-window.png b/files/opencs/menu-new-window.png deleted file mode 100644 index 4a42da0d1288678a5bc4bd01a4713834f3aba7a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 213 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP_W(8 z#WBP}@a&{$p#}vGSHs)$^uoTgA7tmVnEGvoX1z%3>!%tAt}Cx-oFeqH%A{SOp(AOn zXt?jRv)ayGv32JvZ#ABcopUPXz_;0VuQ|n6A9K0&wD|l2aS6jfwst<34O1?yxy3DK zydi13!awf=jv^LL@`8(RT)7eT%${Ml{*m{uI=*idtt{Qkm|Cl~`N^Y5DWH=WJYD@< J);T3K0RY~_Pj&zR diff --git a/files/opencs/menu-new-window.svg b/files/opencs/menu-new-window.svg new file mode 100644 index 0000000000..33262366dc --- /dev/null +++ b/files/opencs/menu-new-window.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-open.png b/files/opencs/menu-open.png deleted file mode 100644 index 3766e17549830ca741e42055f9de51faa0263226..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;jxQ zi(`m|;M-tFp#}vGm)F(5Ch65K@NVz>D)x6iTdze%m~rV@Zk-L%icB>&T|bV^hKmsy{PJL1`?c#)bI + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-preferences.png b/files/opencs/menu-preferences.png deleted file mode 100644 index 4644297ad0dd75a7847000ec23f824e5845c0662..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 385 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=d3- zBeIx*fm;ZK886+f`vVk|Dshb{3C>R|DNig)We7;j%q!9Ja}7}_GuAWJGc|_p9 zb;Q%fF~q`u=_FgeLk2vplYKfC7fe@ae<-uT$iT3~EtJho@4%^2p+9SuczMr`2zYsN z?VtZwt(GqNkg;~^)^E8zmPaJcE4CzmEOdCk@QL=zR*Oykm)1@{T=zVtY0vl0No6|^ z$lX7BeG^x*)07%TTaOBPkK~VA-{g21IGArL|M+i`-ckvvC!zkq>6wkDCt@O)RmGa8<0ehNE3bfxb Z|CL*E(B + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-redo.png b/files/opencs/menu-redo.png deleted file mode 100644 index 0cd0eedcaeb898e79a55e1f572c52256d9d5d481..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Gj# zi(`m|;MGa`e1{Aqj$UUIYI0C&WbywnC$Tt&(c9s(e*uFDyTfLmLKYo1jtHJ~g`W8B zIWKp$C1rm4R$G0)di`6Y^S`^F{;m_1$!!-kyL3#@plN<{nTYFqpU=e&Mgm3)-Z->R zh$s~OY9M~d^p1;H?c&h7l=#PCS+ z#x9>|~QaJOhytpOOywJKnq=q3<%szi> TOZHS?05EvE`njxgN@xNAl(>K= diff --git a/files/opencs/menu-redo.svg b/files/opencs/menu-redo.svg new file mode 100644 index 0000000000..f19f236e7e --- /dev/null +++ b/files/opencs/menu-redo.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-reload.png b/files/opencs/menu-reload.png deleted file mode 100644 index 2b780598c4162eed952e978ae557e465278f0085..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 447 zcmV;w0YLtVP)PGS7Qaa1b3$&sEeJkZubf1&Oe51JYd%SmYmz3wBv^o(wk+$D21{nnZwrGZXc+SW z?9#EOUGQ_ZZSO}>RD<6Z{?7v9$co&fRtdfkP6Xa(l2fKC!{2Ih3qDMZ0v}@AU{6Bz z4(jNO1#)#r3uRV}v;g-K{YpLs<(!;BrWnu@NS^$@qVk*tsTRUcr#b)47VhEL^(1Fbv;Z*HtX0>tt#fj9#%sAm8G002ovPDHLkV1nW%zJve( diff --git a/files/opencs/menu-reload.svg b/files/opencs/menu-reload.svg new file mode 100644 index 0000000000..aef27dd104 --- /dev/null +++ b/files/opencs/menu-reload.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-save.png b/files/opencs/menu-save.png deleted file mode 100644 index 4be88765bef48fc5b571e98b8c942dcca95934f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 302 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1F4L zi(`m|;Mw44p=Jf1Cl_Bo42|*+dhL7Uv*KOXLjgznjQ?h)nJCWA;Sg9_Cvx^rqJ+}c zYf(pM_~> z + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-search.png b/files/opencs/menu-search.png deleted file mode 100644 index 1ec63f0e82853a56d41b8678e540d32c5ad6b33d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 408 zcmV;J0cZY+P)lD$gAKoo`NXTd`Jc>ocFP49~&C^lABR^qz&11?yp zm59X#wel4#Oav`$7oWhqf`y2}HYV|#T@ohljE!EHopaB*_vG$Pg!p4fyFU!WE2Y#{ zS(ZXdDT<=FNYnJduK9^oMbP{VUZ$y?B#Gn>$mii-sv7T2QOvn>6~I4mZd{t-TIPpw zJB&STup`#sJ~*2WZA?W`RC+~(-}KQA%+eTh#$^IOi~i-(3|B4j41DV;I)(3fG*Q1s zp7cu56K8{0HzZ#GpKGnx^E~gsU6M~kZD16dj<`-(sxp6z*yb!@vX4m@lh}}UGz@IP z#u)c8*>Ra@yU>TTEPGsR2Xj*`eH;YA8~LEBt(GW04_C@zZ*?@c3lHwoJtl~mq|ZdV z69!sIKBzJ6!tY4zpo_^h(CnreJhk434>vphDf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-status-bar.png b/files/opencs/menu-status-bar.png deleted file mode 100644 index dfb72b1e13cf674dbce6c9d72f7bc493882cb6a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 188 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP%zKa z#WBP}@NG~uUxNV$^Vh0^$am^ZoGB`5)ip2r|L{!P_R@`UpB@jh%aV)V{ajc=@47y* zVR|#&`010T^k<3ex@Fp0+=ulye8}|Mct*u|y8P;wPY*JcOjh<-X)^hUSoEY9dTuf{ hNj|3YCvUr#p|??9r90x`8xx?-44$rjF6*2UngED1KT-ey diff --git a/files/opencs/menu-status-bar.svg b/files/opencs/menu-status-bar.svg new file mode 100644 index 0000000000..a6b4d15d1e --- /dev/null +++ b/files/opencs/menu-status-bar.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-undo.png b/files/opencs/menu-undo.png deleted file mode 100644 index bd177ce65a62cc08404295d3c98e550cf8280b0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323 zcmV-J0lfZ+P)rf1ZvFO7F0=P1S)EDdcINxOnVs8R&-+XBegA~lT!yPk5k(d9JH8IX@Flsj zIBh_&LUh9?6d%h9vbZUVjRqH`RNEx13Hbd+QS>C-(RgkVB$kpUSFj<%vlN1N$TxTo zt}D4*jQD|%as&YQ0bfYY9GYe9&;(c^tXo8qOj(u%xH}XCftKpr1d`0gOx4g@za{=t z0}A)JbraSt$j&lm=N9=E?{h{2$)yiTf+nWOyodOI8QVZJipM5!Hkvq1=-jYK_yOTy VK0Na~gXjPN002ovPDHLkV1lPsh4=sf diff --git a/files/opencs/menu-undo.svg b/files/opencs/menu-undo.svg new file mode 100644 index 0000000000..1fffc9959d --- /dev/null +++ b/files/opencs/menu-undo.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/menu-verify.png b/files/opencs/menu-verify.png deleted file mode 100644 index a7878ebb3f6c4c77230f8c080c0115dd7aefb765..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 487 zcmVQSK@^4eAtF{Hh@^M-7uZ;7z)DyvLBUEPpkRpF z2qM7;f{l%!2o|vj+KO8UHnC8_GL;ZWIxPf=g&`np*6&U-i&;|`yl`gbo_o%n$F5=g zGt9W(^SnX0ZPPT|9HtCNVdxbO(-+ev*uz{F?$1HP dUz_|ie*;IC(~hX{6SDvS002ovPDHLkV1m_N&5r;8 diff --git a/files/opencs/menu-verify.svg b/files/opencs/menu-verify.svg new file mode 100644 index 0000000000..d72d229edd --- /dev/null +++ b/files/opencs/menu-verify.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/metadata.png b/files/opencs/metadata.png deleted file mode 100644 index 34c78ffd61e30acaef3cc30960d59fe30979cc50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 389 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=d3- zBeIx*fm;ZK886+f`vVk|Dshb{3C>R|DNig)We7;j%q!9Ja}7}_GuAWJGc|_p9 zb;8rdF~q`uYVbkc!wNh-S6BXe!Er$1XZhB5lilTFn|}$k$2?+bN|AK`_H##Y%0=Pi zGbfaqOl&Nte{=o3IEKSujtOtQv4#^z&}Z)2DEnZqRMCC6Ep$|)Sbu~y#5zhbWEi_K ztmey;%ICH@yCCoX4PA+OX$h@+&Mqi-oPY6NuH=Kdf`h@*dKZdWwa+U};AYTX&E{Hb z794Q)wJO7WhcEkXcD*$Ez~FEU%PZr9_qKJsy8l!9 b=5OW&4^?AKLb7;(eq!)+^>bP0l+XkK>c5Fr diff --git a/files/opencs/metadata.svg b/files/opencs/metadata.svg new file mode 100644 index 0000000000..acccf4f4b7 --- /dev/null +++ b/files/opencs/metadata.svg @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/miscellaneous.png b/files/opencs/miscellaneous.png deleted file mode 100644 index b21f6e214104dea351f88a07146c7ba39a7a0ef5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 466 zcmV;@0WJQCP)j`*G%o6Ip3@pIU+alT>M0xlkk^ZUQfIINLJm;K=`oy>00itedRicMbo*@=7 zu7g@)f$k;cQ4>5!M^r7#GHm;m{8@j1d^0e^rT*-I?nm%nL=K^a;evb)^BVaooa)W< zYXZzUDjGA5Fc6Jlt}yl#Tsa^EY<@uRM)-(uI55L4!41M?;XVRW7{gC^Lb!$ST>{M8 zyuSpOi84#qm;mu1c@5#dNbZ-wC~vmmw2#PRBhEAC2A#U3;(i76W6VanLIq85fO#52 z4f6|kO)?(R#HQMT#2uLH;2q|PSVv1y6Pyye#1Q66OFi-W4a7I1tasx* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/multitype.png b/files/opencs/multitype.png deleted file mode 100644 index 05676e2de06f52ed563842c0d8d5fe888e771a70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1708 zcmV;d22=ToP)Ekc{Xm)1$i(h%(W|y+oEr<7?dfwtd#j#`W=aP>d z{Klc7H5<=7{e+;hy*X@yKVZUq?i4 zwO3e@!G|P^!(or}?!k~`kEAO{$F}cz#3sg{3&0P1(d=|*h1f=?&#>o%AFaH$e$%&( zCcpYbeo!8L>X@CoEdYlP$VMk_e_E;SmyycuyWV|&ckAwbwN+#9acONB=QAe%`g85g zH&E@iod6;bn-{oqZ9N@%@ZheOe*Kfv-#ZvS|D|L3b4wE8w?86b)mZh-T_4^*G%~g$ zYpfX~Bq?T~8c|g$Bb#~Y@B^Is*HscP?7XYa=6&BGojHTWoS3(ZU(D&M1C7<|2DaS${;V-n#adHzn{ShMW>FI)Q~}3ey~f9%Ji_B&dzPWK zTZmWA5(YOQJ|NB^;t?Qdi|sq#n|cZRmn6b8Ngv;K--l|2#|kb1pCO@A7*DDgAeu8f z^-tQD|IXUGKfvTGFYw0rPl>*cq_qNsbBH(^!-K(v{_7~09e%z5%qC6DP1+f;|v)6^AAy?KhWT4UYl2y54^&(gnT2X6_$JOAlM zqY+M>_!B2zyF^81@e)xSleP&p2O_Mh3{a5_!=VIBk|G(47@`O&fp%EUwdr>_e(D?# z@7+z_?e2c&A<3S4!P*Pd0b5n8X576Y<({$4xFkh`+!8k(t3f2FBiuD-Sav3;*upcBMI}oUzpb00mn@`^GyL18u-r*S=7+t zfR-MD|0e#6fK%<<`a~om0t!CNqd(3QWSnrDo zP6WmEW+KjEIE)goD41YkD2k~s!)Il&fMy3LB4TD1ZuB09h&Z1i&f_DP12I9=VNSs! z4#Pw+bAY1iu7|DY;g~nX6y}ImPym_jTxUMa^FCmRgcK2nx&pB?K}$ZwT;Yi*Vg{y& z7>Xh0vEp!69?}fM3h|lt1O85n(BpJIfPSKi^GIpb6uei=9EO#Ki_Iy|1me zH!ISwq&|qlK(4k(04E{^fRcUzQbPpI5pg&dzzp$L9K}S6%?yfuXGn>mXT+h!6tf~6 zW_`su?=eHJG^vyc5a%-Bfr=Rp%jshhK?Iz+UZcPSyjmZSi2yoBt%{j|7;~`Uo$qNN zEA8cBUP>nIs1`d-sv}!5F)YSDomea@RV+iHn3maMiii?b3bCnTQnW}ZQ#y89#2Ed= zH0@pq2sidYy1#ohKjVM!-5uA7TfQL}uU_XFlKfBGWrvVStfP3p1E6w$lx?ICV zh*z6j0Zs!`K;HjjA|kE?k(A6VKLv%?InV_nVE+cordT0F3*6fP0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/npc.png b/files/opencs/npc.png deleted file mode 100644 index 5b5b199be6186c4f92444bc7c541ead7c432b1e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 465 zcmV;?0WSWDP)n;ZJAq-;U_^C_I~x44It#4 zD2aIvJm^_64{n7djq_agg1#oa*#JW73TPs<5sfH{ri9D~Lk81x1>dW9%?1p(Lzop2 z6TQ(Q<)NJa3VeNRUN;+HIskPb?^=eOkObD?<`7{h{nvjy)oL|Cn6`G5NPJ&P>1%ZO zTx(3cM(kG6meG6Rb#o^hz}lBQiz7sVHE8ZYkY{tMhHOAe?2;E$%x9V}_ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/object.png b/files/opencs/object.png deleted file mode 100644 index 4f552151dbd78992f83ed7939f25356e38fc4f79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 447 zcmV;w0YLtVP)7PS= z*9Sq+2ez%0S|W<&BuVxuw!$#{C_oa&ED+Zm$9V#)q-nZ_$RkFGD%iN|y6VL5Km^w4^@K7r`^EyY56&bcZxpns_ca?oa) zMDuE1;wh>l8)eQ}WImHzx3JEu8KkGNoX~NCGXr-RLQP27?L|@a;`_ehVj7<3wW;ra zb3i-_M_%Iw$G;Iy@g+(Dp$=h({#uj=#D(CDDPn=OA=c&)A7?0!F9rf);xR1~RRmpv pA(9{HoY+O&6wG>H_+t}O{0UfliPDznHH!cM002ovPDHLkV1iEIw{idg diff --git a/files/opencs/object.svg b/files/opencs/object.svg new file mode 100644 index 0000000000..717269b9d6 --- /dev/null +++ b/files/opencs/object.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/pathgrid.png b/files/opencs/pathgrid.png deleted file mode 100644 index 710ff1357671fa93ec555d6aa6d8371cd3b7bc93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 407 zcmV;I0cie-P)TYau?D8ExwHVX%@F@`6X(dOv@DZHsZ3MQacNQhhSp_>k)V5${9(#QB-cc&txT0a}923tyX z1*C%Z_s#@ue_?ZoO9R&48}V_XsJ7xeC2p@#{R7`+c|HR#b1?t_002ovPDHLkV1nXj BseJ$d diff --git a/files/opencs/pathgrid.svg b/files/opencs/pathgrid.svg new file mode 100644 index 0000000000..ba16efcacf --- /dev/null +++ b/files/opencs/pathgrid.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/potion.png b/files/opencs/potion.png deleted file mode 100644 index cb173bb9ed177e2f3023c14e1f5ebb287ce4b6a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmV;r0Y(0aP)jWg=2$J kPKlGAKL|sL9m(EaKL|R9ppk7OQ~&?~07*qoM6N<$g6eX=b^rhX diff --git a/files/opencs/potion.svg b/files/opencs/potion.svg new file mode 100644 index 0000000000..b59d12683f --- /dev/null +++ b/files/opencs/potion.svg @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/probe.png b/files/opencs/probe.png deleted file mode 100644 index 2e405d365324ef5da69e824106dd73ff5523bacc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Fqb zi(`m|;M~c(c@G(gxF&04WH4wZF)d4IedFjW+0r1|&^AfZh-LQ&;Svvz8O4iLxaQZ) zQMa|+c7Nx4t4+PnB^(7LCTuobmcRDvExj71`-#_{FitF7z0QH}o?GCShTRVzC|Xbb z$l8A9xr5M0ZPrr>b7H(%g?_lG-;6Vw|8Z|n`oW&Kg$_*dKLTwbLfU z&3hL{#Bx7&S71L~7U7{LEzh)WgNae2T7lXHkv`Uj(z*Rl<{A5MeIc~#)U-E~WW^2E z=3e}I^oY9pj+;xROOO9BR9qx(rr=(BaLYSh-Y2oUx#tDc0)5Ni>FVdQ&MBb@09{0M A!T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/qt.png b/files/opencs/qt.png deleted file mode 100644 index 381cb2251fcb072234d49752cdc5e1a79ff6e675..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 405 zcmV;G0c!qPx#1ZP1_K>z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy8gxZibW?9;ba!ELWdKlNX>N2bPDNB8b~7$DE-^7j^FlWO008Yt zL_t(IPhTD(84M=q?cdCl(i2XFH5286JKydjx#l+M23-Aew2+z^VBE z*Ka`BFzj + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/race.png b/files/opencs/race.png deleted file mode 100644 index aeed2fdf3e9d06145a1d295db4de35a82c218159..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 451 zcmV;!0X+VRP)SNxYz;AK(%dS)qf! z;&WQailP#BIwFi4WR+^hNoMKYGq4f>*u{6OFfD3~(l#v#1vJhj>d*MKH3adBF&w@I zc4~Mb0AU|sgzNSvjG?BQvAb$7_`rLA3};IXj$}Yw#TOxF0z;!-++K(&MrFjsldLtw98R&h7uk$c%vkwEJ4JXmY(by9VWY`U`51e?PKoxJv*4002ovPDHLkV1jxF#RC8U diff --git a/files/opencs/race.svg b/files/opencs/race.svg new file mode 100644 index 0000000000..cd7778127e --- /dev/null +++ b/files/opencs/race.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/random.png b/files/opencs/random.png deleted file mode 100644 index 2667630f5c27ba416a3810aab36cbf9d8e872b3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1892 zcmV-q2b=hbP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf5&!@T5&_cPe*6Fc02*{fSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@H7+qQF!XYv000KlNklpRqG7~cP6+fQxn#$KDE z5D_zj8DqvFA*T*9MJ1UWMOQ{X1xHX-0i@If6F$PdRYx^tNRh0cR&iiKn}ps=nqHFr~ue=tqpc zf2|ihDwU1^dmYjbB(uu3!5=r)L2<8!{HoFVuo36;Pr;FEuj2DK;o5D{5HMY4 z60j-Ie~L)P)Z{oOl<#H*uen(Uu}B1|REpr>VA$B$;M$c-8s%#6<~vnrtgS#p%?&iw zRicGd#nnYH^^37lfSQDcfLJI{*4N%fU3DeKhX-Z_r-o}){0&D39>iiXcsw3fnwg=g zzLG7wKJdG@uA#2tPh@ALAuTB$HddBO9Rgw7w=qyn;zB_#c5IUnkj6QCo`32%aUPs512Z)V~KuTgfUOaoq&bfNj zwB4?+=h|hjC@aQ}tw9L%^+Nv1OdLu{KuJ+9e7!sv9RhpfVwkf9`B-IX38N)T2;g%x z)K+0za1bsQ=CX5c2zW+XGS(5*Co>P?Qb7(DFIohXrAx6hR6?sUsY4(>Hk$GB^h9`g zIGhCngh;kv_;okCaL$}K2C@@yucmxfx2u(3{p}pY&V0zCcH^&-B3!*($P(djI1mxz zuC`i*b?#za0)Y1VogPkr6iddBf99q>n%(GfEZ?-Q_?3v>3l%I;R1D^YXI?UUfc2 z)wNQxSORb=D}$YLsyy>hD!e`2k$YT@>sKyJ1E)PtNr=Ob3m1aNu|r`_CN-fhfw<^B z%*fDNP1)7TwZVJaTZj)Hu`Bn+)nYtss6h=;e&)mxlwZB97yNQz4ld+nqbN6v1<%Wt z!$U08B@i36hxz#a^M#uWoNm32miz4Ht0Yc01Uxq@9quA$T>>$YQbzgiwL#!zB^PHc zJ5_#$sMS_Z!E-Xx$TsBbA+RTc86J4W8g90-Yb$S6G?1lr8b{Lh>-4;Ul$xwtn3$N5 z{>QRqSA%EEQ{gJqLm*nZn|c4Hci!MnoA2U$UJm33lGstBZl{K$%7p?aaLL#Sg+e%R z?Wcifr6-dyb=0julr)?f?C;jNUE6Rol~eGRrg{|RokV(S;xuq-I42&Loq6LgV@JzQ zl~eG{w0&^lJLnQ1_n&#&*Ts(7e83+!*5Yh#HV*EO*C@GVBzWGIFKY`%RY0|w0o{rPQlZYW#HM_=-vW9AFr`CGFm-t z|3)kIs8PeI%TAS3@CSFRk)M-=)WleWz|;1{z}DJwOy?GG?bk@cLbu{2Sv0ftTpRp; zeHFQIN0A&KrFYb3mYpi6;Cu&rn3^orydkDVGJhYhIO1ScaZ&!4cr{&lMOg{f*xJIH z9F&@ybOUB0Nft@^d=RF~mtlpODG3}hot{(hom)4-Vuh*7+H$4-NT!|BoE*3c$>t4H zx2~76ZdWU(r#U1f1hyoCkw*wwLK#SmmBLNr3=49^1bjY;<6Mo~Y0vjY?E;@?KV@#V zeC|-HO@c;g8mXmWp_-m2#zn!B53 zfR)8c!(+KN5rHF5?!d8EQNuOWRgh5eD;rqI6Ad{>4?`BYi@k(2aL_AC!FPvlp(CiW zGGF=a50jZM8e&p~*IGBwz;QnB1PPgCIC(T3vC^Gv&uOWX;-ui(+0b79cTcmKZE+V1 zhqjZPcjJ#sC_Hrx2{F5II3=Eh3>R>0SO0&HQ*9#Ft1KioR;xY{!;P<5Z8dIfX|7zc eeAzcW&;J1sm8E}EEyuCpMi}?|hzR%iZHk zJe59AX}ey$?yK3>CC^Kq_8)j-tzNL~$78dD%Fo=6@~nULg0EP3bK>nYJH0jpDfk_B z?N<6cd*eEXmt}%(33VU%8@z4aefoEgOJv8ax$HYvzI8WheW;ccdfct`nvGk>|L>+n jOj2c*S;ykkx*NCy4{KiiuC&J#=vD?#S3j3^P6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-clone.png b/files/opencs/record-clone.png deleted file mode 100644 index 262a67a42400f360345c578cd216bddb8bc6e06e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Gay zi(`m|;M-tBzC#8)J-36*)UMfS{*<>-{wXG%fEMn*;m#ro6@SrUh1_vcAE0Tc zo3F>>yP>_`U1R2<|IB}q*e`!Pl(xH_%`E;O>rVdblLdLtFr08|GMM+OYG=shhdPJ9 z>E!Zueml^#>)eN0IYy?;uvN_=!Ha5mc7IVz2-PzbunHCMnKCiyh=lvf-Hncway!EP VX4GnG8Ua1T;OXk;vd$@?2>{spXMX?y diff --git a/files/opencs/record-clone.svg b/files/opencs/record-clone.svg new file mode 100644 index 0000000000..3014b2b4e0 --- /dev/null +++ b/files/opencs/record-clone.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-delete.png b/files/opencs/record-delete.png deleted file mode 100644 index 817988a5d1a4cf18b48b67d4b74fdd4b6fd3ed46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 151 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP|(xU z#WBP}FgZbj^{@hq#KgY^r&WL0cQI^InR52Ti4z~*|F2KmYMcGPu_a={RRd;KPDVF2 uH@2y^*#~ABUSW!LxWU2`yO@bto{2%Zg@;pGV;L{dPzFy|KbLh*2~7YHqbe@| diff --git a/files/opencs/record-delete.svg b/files/opencs/record-delete.svg new file mode 100644 index 0000000000..9a21973542 --- /dev/null +++ b/files/opencs/record-delete.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-down.png b/files/opencs/record-down.png deleted file mode 100644 index 92fe788602136102ed33598031e7493325540320..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6Vm)0PLp07OCrD@=n9jLfMwywJ zxjA1t`04)tJv}Rg{(my}cbakW(Qm$^Xa4W2d~t2d&$@4|8{>bAB^7(_ejmvO1ov;B xxTNvzSDAr@K~2Jq-8-eX*H_hev&U;NGbG-S&@#X6+Yhve!PC{xWt~$(695OAIXeIV diff --git a/files/opencs/record-down.svg b/files/opencs/record-down.svg new file mode 100644 index 0000000000..9febe57065 --- /dev/null +++ b/files/opencs/record-down.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-edit.png b/files/opencs/record-edit.png deleted file mode 100644 index 8b94e163410880ebd3efbeb68b3aa23e595d2c65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf67JIrlhG?8mPEcTwkd%;+`18+M zl81-q|HO#dKz@Y69AAULw*Lit|NRHa&fti2+{NUk5t6#=+QaYj4>GoMYWeW2EjKeX z`0+OVpyuBr?{BDSi`uizFn>6SN6Sa+Ezixm|B{ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-next.png b/files/opencs/record-next.png deleted file mode 100644 index 76866e4734c63dec80da28d7ef38c1def25bfa37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 248 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;ix} zi(`m|U~+=QmIK$XoA*l`H+&d$FVdQ&MBb@0N+hhmH+?% diff --git a/files/opencs/record-next.svg b/files/opencs/record-next.svg new file mode 100644 index 0000000000..bbc44e7459 --- /dev/null +++ b/files/opencs/record-next.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-preview.png b/files/opencs/record-preview.png deleted file mode 100644 index e3adb6ede41e99738350327d8717ed043c7bd82f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 334 zcmV-U0kQsxP) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-previous.png b/files/opencs/record-previous.png deleted file mode 100644 index 2009d84e04a2571e0f1ea41facdc882b0a50b24d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;j@W zi(`m|U~+`PtbXg~J7mVmU$%XL}t!8Yo;lrq-|1mdFrLEF93td?C#@rFF-R8+#P^LUdZZ znRi*VF*?k?+}Op(7+b_0YiMS7j)6(afOo|~&JI%vNy-2Dx3+Muj^YtNki)o3h5N)k z-Xr#nYzJBlGv=^0gfQ`*WDPPlD4D3m@a0^HLYi8reC-ux2AP!_#>qu$4}sob@O1Ta JS?83{1OOiSTl@e3 diff --git a/files/opencs/record-previous.svg b/files/opencs/record-previous.svg new file mode 100644 index 0000000000..2479c8bd49 --- /dev/null +++ b/files/opencs/record-previous.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-revert.png b/files/opencs/record-revert.png deleted file mode 100644 index bd177ce65a62cc08404295d3c98e550cf8280b0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323 zcmV-J0lfZ+P)rf1ZvFO7F0=P1S)EDdcINxOnVs8R&-+XBegA~lT!yPk5k(d9JH8IX@Flsj zIBh_&LUh9?6d%h9vbZUVjRqH`RNEx13Hbd+QS>C-(RgkVB$kpUSFj<%vlN1N$TxTo zt}D4*jQD|%as&YQ0bfYY9GYe9&;(c^tXo8qOj(u%xH}XCftKpr1d`0gOx4g@za{=t z0}A)JbraSt$j&lm=N9=E?{h{2$)yiTf+nWOyodOI8QVZJipM5!Hkvq1=-jYK_yOTy VK0Na~gXjPN002ovPDHLkV1lPsh4=sf diff --git a/files/opencs/record-revert.svg b/files/opencs/record-revert.svg new file mode 100644 index 0000000000..11920a329a --- /dev/null +++ b/files/opencs/record-revert.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-touch.png b/files/opencs/record-touch.png deleted file mode 100644 index 808e1b6c4b40b233d4ccd470fc4958386d33c8bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 289 zcmV++0p9+JP)PEE!@CU zBv}dg!@$fxGe5Jl3>zXWow}|Qgb-FXq?AD0w%-ev0ww6yZf*L6{}V{jEJd?F38ZM4 zqTL+!vbjHR_fj-0nP7}biZ0PdKl_uM`%+wiGu)x+a1GvZv37z`dBh>+9eR#~P + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/record-up.png b/files/opencs/record-up.png deleted file mode 100644 index c8bfa4a34274561e236984ebc4d486d05017bd71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6Vm)0PLp07OCrDHr=sETEZQ%9& zr98`-?(?x(vuT!khNyD9EW5v%2MG3UujSt8x&3F|x7LmE$r>MXKi0M%`u~15d!nIJ xiE-$^-{((stZA9}F#g{&PlK;MJkHV#46n~i6gX-JZv|S!;OXk;vd$@?2>`lgIN|^R diff --git a/files/opencs/record-up.svg b/files/opencs/record-up.svg new file mode 100644 index 0000000000..351cb14d4a --- /dev/null +++ b/files/opencs/record-up.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/region-map.png b/files/opencs/region-map.png deleted file mode 100644 index 7631847beef75552d7e2b1949259627987600c98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 428 zcmV;d0aN~oP)lpYv1?F3dfp;iHt}kX!rUZtv1xl#xXO414!0&U0@qnv?^=M45CNIfjVPRmCG|D z=mLT^S;hHfLLl#18)`DqYXqKo*N)?uae^rU-w_jmXWp|$${Fe2eqvU#Q+!enb~CdV z>Fp&?Y0=FWStmpSrPGJYDWGKpkaKCBc{u%y41luSs=7vL&$5c%37?6htE z`_NyTN&o>+-#$39Yzk W4f$`6a&e*n0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/region.png b/files/opencs/region.png deleted file mode 100644 index 2ebaeb02850fe497f5ae24daef767139916e2fbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 605 zcmV-j0;2tiP)Px#1ZP1_K>z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy32;bRa{vGf5&!@T5&_cPe*6Fc02*{fSaefwW^{L9a%BKeVQFr3 zE>1;MAa*k@H7+qRNAp5A0004?Nkl3&4 zKk!Qef*>(s(8?|rTKK37b!F(cSmp!zE>IcSXc12RDJNUdYGI>~`UcSoF2AGl1)26x<_0EyjB|^!!pf!M z>)cWwoHYiq|4+acSk7--Kn1ID3zeS^5tqFuhlq38@G1WKjCq@%?{UFO)xUpD$%3c- zhDan^H}>i80qLnU1zlN@r>{~O#F^p56I8BeGMVv_eIPwO?yoe54P_3&-00000NkvXXu0mjf|El^g diff --git a/files/opencs/region.svg b/files/opencs/region.svg new file mode 100644 index 0000000000..4458c7e376 --- /dev/null +++ b/files/opencs/region.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/repair.png b/files/opencs/repair.png deleted file mode 100644 index 1b5a9ccc11a5ca12b2589ac4700a344148164d88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 367 zcmV-#0g(QQP)Y01WK2NB3Fo%#|vBX=dNN*`3{4rS9G=&-1ipSxQjH zwAPv2l|mK|VHn1BHV~~G?ovD>9)gjORYpOR{400?ecQHG);EbChz00HQS^(yF*J{k z<9sJcav=E*KETEZ5ln(0(D;ra3jzEW=oY9fym~nVE!a<5Yo}?tCZ2-^h~WlsjlViX zb1~od&rf`zR2e5}yPz>)osqhxihwKhaAR6s6?uu7)=|2^_AEPGMXqS3@JuW(x&$(b z9lq2! + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-icon.png b/files/opencs/resources-icon.png deleted file mode 100644 index d84c90d5dc841c6b893970f5d791205528ba7f42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 194 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP_V?) z#WBP}@a?3Fd<_a5uBy@X%k5u3-|6^bilFZ8FAJyF2=2MpY&0i2Cg6{u+a(#LPnxPV zmyaxFwA#k7TC`U9RLX|(_~p!3;=exL)xYKR(U~kx9DS=Nt?lVxXZ}3-nD4ou35ws# p%}bYXCaC^+p0Kp~?C-xab7HTCtlSkQDG0Qm!PC{xWt~$(69B`5N6P>J diff --git a/files/opencs/resources-icon.svg b/files/opencs/resources-icon.svg new file mode 100644 index 0000000000..2054ba0b2e --- /dev/null +++ b/files/opencs/resources-icon.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-mesh.png b/files/opencs/resources-mesh.png deleted file mode 100644 index fdfa3528b1314612ff417ff703f78f534d905b75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 586 zcmV-Q0=4~#P)H27(lHr9~iiGm;ivAZw9}R+b4B)AV`9W>6O$_i8TlG zy5S^WiCxkBFv%D_EY}|`zl9zq4`M#C=74zQ-G1qsqMjth)07MDq|3-_?DNsx;NPOH@g+vY{!P!?vx+x6@1Y-H z2L=s4ZIh>Tzx+FX>3{?@W$pZbBN|R>_)X`@E^8X%m)MdG7H154?wnQcXl$&zIN#KN Y0mg952>$ihJ^%m!07*qoM6N<$f+7YBbpQYW diff --git a/files/opencs/resources-mesh.svg b/files/opencs/resources-mesh.svg new file mode 100644 index 0000000000..ba93da8091 --- /dev/null +++ b/files/opencs/resources-mesh.svg @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-music.png b/files/opencs/resources-music.png deleted file mode 100644 index 53775109c5bfe6b2f7981aacd9bcf9526ec74002..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 354 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wk0|TRo zr;B5Vg%$VZ%>fhhr<&qv-{{t!633XgThcg)hE;}!1OLs8UZuru3cDDI_woC@b z2@iXE1dcy8VR+)QKrORFY=fGJJ_9eS_d`>Lj9|NfP`$`F21~&gT|x^OS6|r6u<6$M z^ZYIk`IslDT;N{t;t= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-sound.png b/files/opencs/resources-sound.png deleted file mode 100644 index 86871611f5deae184cf6b986797020563c5c3ce7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;i2$ zi(`m|;M7Thd<_ab&YYdcn@< + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-texture.png b/files/opencs/resources-texture.png deleted file mode 100644 index a96c4bf8c7b8c1334857e79e33ddb9230423c3f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 288 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1Ftc zi(`m|;M%~8LWdPNR_}iI`OWcH&r74-SDXxte!pw?M+G587SGe0awbQ5alYG@JA1mX zFlSzPo6D7RJ=IDj3$!ZARN}k}Pbx789-Q;}_96Ep1^pIDHyRV#|Jd)DVYpY*@*z9Z z>kk{3q!`WLdQF^*H~Q?^T%O~PcO>N)|BBJcn|pQ1;lGjnFA7qX=j>Es5U~5GBK^Kl z=7>S>hIT90J15KLXuEC`3eB26@s0K$hY!|Yvxp`=!nrE9M=(02@;u6{1-oD!M + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources-video.png b/files/opencs/resources-video.png deleted file mode 100644 index d86bc6025e729f7eee1a5e4d3e9fba20d30dacc9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 277 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkQ1G~? zi(`m|U~<9&0fS#ZfBt2kdGN#uk4b_5|2J zp~Sbuu*A+oUwD^7&-o)qN_aCoqy>r(9NBQug7?Sw_wpJE+KsdJG4pyJUM8v1c9~&< ziq4WowzDi!MdBT~4v$VStY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index eef5c01bf8..786040623c 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -1,109 +1,102 @@ - openmw-cs.png - activator.png - list-added.png - apparatus.png - armor.png - attribute.png - list-base.png - birthsign.png - body-part.png - book.png - cell.png - class.png - clothing.png - container.png - creature.png - debug-profile.png - dialogue-info.png - dialogue-journal.png - dialogue-regular.png - dialogue-greeting.png - dialogue-persuasion.png - dialogue-voice.png - dialogue-topics.png - dialogue-topic-infos.png - journal-topic-infos.png - journal-topics.png - door.png - enchantment.png - error-log.png - faction.png - filter.png - global-variable.png - gmst.png - info.png - ingredient.png - instance.png - land-heightmap.png - land-texture.png - levelled-creature.png - levelled-item.png - light.png - lockpick.png - magic-effect.png - probe.png - menu-close.png - menu-exit.png - menu-new-addon.png - menu-new-game.png - menu-new-window.png - menu-merge.png - menu-open.png - menu-preferences.png - menu-reload.png - menu-redo.png - menu-save.png - menu-search.png - menu-status-bar.png - menu-undo.png - menu-verify.png - metadata.png - miscellaneous.png - list-modified.png - npc.png - object.png - pathgrid.png - potion.png - qt.png - race.png - random.png - list-removed.png - region.png - region-map.png - repair.png - run-log.png - run-openmw.png - scene.png - script.png - skill.png - start-script.png - stop-openmw.png - sound-generator.png - sound.png - spell.png - static.png - weapon.png - multitype.png - record-next.png - record-previous.png - record-down.png - record-up.png - record-delete.png - record-preview.png - record-clone.png - record-add.png - record-edit.png - record-touch.png - record-revert.png - resources-icon.png - resources-mesh.png - resources-music.png - resources-sound.png - resources-texture.png - resources-video.png + activator.svg + apparatus.svg + armor.svg + attribute.svg + birthsign.svg + body-part.svg + book.svg + cell.svg + class.svg + clothing.svg + container.svg + creature.svg + debug-profile.svg + dialogue-info.svg + dialogue-topics.svg + door.svg + record-add.svg + record-clone.svg + record-delete.svg + record-edit.svg + record-preview.svg + record-touch.svg + record-revert.svg + enchantment.svg + error-log.svg + faction.svg + filter.svg + global-variable.svg + gmst.svg + info.svg + ingredient.svg + instance.svg + journal-topic-infos.svg + journal-topics.svg + land-heightmap.svg + land-texture.svg + levelled-creature.svg + levelled-item.svg + light.svg + list-base.svg + list-added.svg + list-modified.svg + list-removed.svg + lockpick.svg + magic-effect.svg + menu-close.svg + menu-exit.svg + menu-merge.svg + menu-new-addon.svg + menu-new-game.svg + menu-new-window.svg + menu-open.svg + menu-preferences.svg + menu-redo.svg + menu-reload.svg + menu-save.svg + menu-search.svg + menu-status-bar.svg + menu-undo.svg + menu-verify.svg + metadata.svg + miscellaneous.svg + multitype.svg + npc.svg + object.svg + openmw-cs.png + pathgrid.svg placeholder.png + potion.svg + probe.svg + qt.svg + race.svg + record-down.svg + record-next.svg + record-previous.svg + record-up.svg + region.svg + region-map.svg + repair.svg + resources-icon.svg + resources-mesh.svg + resources-music.svg + resources-sound.svg + resources-texture.svg + resources-video.svg + run-log.svg + run-openmw.svg + scene.svg + script.svg + skill.svg + sound-generator.svg + sound.svg + spell.svg + start-script.svg + static.svg + stop-openmw.svg + weapon.svg raster/startup/big/create-addon.png @@ -112,76 +105,59 @@ raster/startup/small/configure.png - lighting-moon.png - lighting-sun.png - lighting-lamp.png - camera-first-person.png - camera-free.png - camera-orbit.png - run-game.png - scene-view-instance.png - scene-view-terrain.png - scene-view-water.png - scene-view-pathgrid.png - scene-view-fog.png - scene-view-status-0.png - scene-view-status-1.png - scene-view-status-2.png - scene-view-status-3.png - scene-view-status-4.png - scene-view-status-5.png - scene-view-status-6.png - scene-view-status-7.png - scene-view-status-8.png - scene-view-status-9.png - scene-view-status-10.png - scene-view-status-11.png - scene-view-status-12.png - scene-view-status-13.png - scene-view-status-14.png - scene-view-status-15.png - scene-view-status-16.png - scene-view-status-17.png - scene-view-status-18.png - scene-view-status-19.png - scene-view-status-20.png - scene-view-status-21.png - scene-view-status-22.png - scene-view-status-23.png - scene-view-status-24.png - scene-view-status-25.png - scene-view-status-26.png - scene-view-status-27.png - scene-view-status-28.png - scene-view-status-29.png - scene-view-status-30.png - scene-view-status-31.png - scene-exterior-arrows.png - scene-exterior-borders.png - scene-exterior-markers.png - scene-exterior-status-0.png - scene-exterior-status-1.png - scene-exterior-status-2.png - scene-exterior-status-3.png - scene-exterior-status-4.png - scene-exterior-status-5.png - scene-exterior-status-6.png - scene-exterior-status-7.png - editing-instance.png - editing-pathgrid.png - editing-terrain-movement.png - editing-terrain-shape.png - editing-terrain-texture.png - editing-terrain-vertex-paint.png - transform-move.png - transform-rotate.png - transform-scale.png - selection-mode-cube.png - selection-mode-cube-corner.png - selection-mode-cube-sphere.png - brush-point.png - brush-square.png - brush-circle.png - brush-custom.png + lighting-moon.svg + lighting-sun.svg + lighting-lamp.svg + camera-first-person.svg + camera-free.svg + camera-orbit.svg + run-game.svg + scene-view-instance.svg + scene-view-terrain.svg + scene-view-water.svg + scene-view-pathgrid.svg + scene-view-status-0.svg + scene-view-status-1.svg + scene-view-status-2.svg + scene-view-status-3.svg + scene-view-status-4.svg + scene-view-status-5.svg + scene-view-status-6.svg + scene-view-status-7.svg + scene-view-status-8.svg + scene-view-status-9.svg + scene-view-status-10.svg + scene-view-status-11.svg + scene-view-status-12.svg + scene-view-status-13.svg + scene-view-status-14.svg + scene-view-status-15.svg + scene-exterior-arrows.svg + scene-exterior-borders.svg + scene-exterior-markers.svg + scene-exterior-status-0.svg + scene-exterior-status-1.svg + scene-exterior-status-2.svg + scene-exterior-status-3.svg + scene-exterior-status-4.svg + scene-exterior-status-5.svg + scene-exterior-status-6.svg + scene-exterior-status-7.svg + editing-instance.svg + editing-pathgrid.svg + editing-terrain-movement.svg + editing-terrain-shape.svg + editing-terrain-texture.svg + editing-terrain-vertex-paint.svg + transform-move.svg + transform-rotate.svg + transform-scale.svg + selection-mode-cube.svg + selection-mode-cube-corner.svg + selection-mode-sphere.svg + brush-point.svg + brush-square.svg + brush-circle.svg + brush-custom.svg diff --git a/files/opencs/run-game.png b/files/opencs/run-game.png deleted file mode 100644 index f5038654fa1a3238be2f3124e12c06e75e206773..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 642 zcmV-|0)737P)e5SHDZcKoowLq~M@~ICt?c(50iZn{;z2E~1kPPC5%Z zI_RRKh@*dmx=4jiPNIvmIw%Nj?#}PhL{m&lle=6fT;P(+{d(W`<-NNXB6m3joB~b( zr+`zyDKMc5)XzSrs)p`q0buMKBsR3x(45S4=GV`^Bx62F5x!uZ)z{6Lw$6G*=4ull zDI>eg4@mN`gO>(~ghjvG|&9nJxE-j)Fz5R0#@>#ijNlJqYDxT zNxO-sv0Z3vFrH5&_#4JAW4C%3d)|!sbBK>NzJ`jAh0JkdHrgi;1N)5+ zFX({8AxfdNq%s1Dz&OR#w!4p8p|r$)3?#@ip3K8(-cL)`5Fd9LL;f{Y{sjgRMQO<_ z+NKBFhrb2GyDauF=qQE2DZT^cPxDx=m;fUd)JTTiD}YvRlRF8lj6j^d-T?eMfIsC? zJw4Vwf&M|JQtuXkU#7tnsoNvKu;EFhtAcOm_4850tPnsl2gQ500MZ0_l|~r5N+l5Q z>m7hs?(%31b!wq<2=x72;!slC;z`P$@v2z@%FhW!@Ld2uHjXx9L1P5|{9LX8{6~hZ zQkt|0;3q}>2fxG5m&)? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/run-log.png b/files/opencs/run-log.png deleted file mode 100644 index 463ead176d4c5e367262864fcbcf4e5a19d23ca8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 294 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`?>!lvI6;>1s;*b z3=G_YAk0{w5XjGVuN& z)o`BQMRLN785QCkyc;B4)-e8`JbCheX5LKZB)*j94l{-of+-9<%MbD>o@Q8)?Qn0C z;^~GZOH?L!-(%d!ZprqCHS3JxO>Rw1O@-*`OorT+jTHhb0;j&oP_b4FoFlGb#4yG3 k&)>hDhYYqVtY>04?6v!2)}vJ`fv#onboFyt=akR{0N)K>X#fBK diff --git a/files/opencs/run-log.svg b/files/opencs/run-log.svg new file mode 100644 index 0000000000..404f74a6d4 --- /dev/null +++ b/files/opencs/run-log.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/run-openmw.png b/files/opencs/run-openmw.png deleted file mode 100644 index 1033d62baa120ce5c3ae4e2da94d122f88051a7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 388 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=d3- zBeIx*fm;ZK886+f`vVk|Dshb{3C>R|DNig)We7;j%q!9Ja}7}_GuAWJGc|_p9 zb==d%F~q_@`Op9V_RKs6Mn;Ejwn!AzDzNdKKKJL0mxr>C+%oock}3-3zi-zsP&|7? zqUkEbf@l9X2{_+l*vM(uTp-P0#M1HfSq=Ne^2QqsvmIG@8FsU!T*%qrnCoz+F-PDc zTLbT`uc8?z#3wjrRxk^uY3oV7lA5qR$D^3>j`ahEI`$)SOC%Y-9eTdsVUF`6@qK@$ z&;BjEq*7C0^8}HO3mG;XS5|mMnK%gvaU7p8dBQ)%-;-sSTRKm&E0{(kGg@W~CkuGk ZGJNsgqSE=Fa~{x344$rjF6*2UngEJofxiF% diff --git a/files/opencs/run-openmw.svg b/files/opencs/run-openmw.svg new file mode 100644 index 0000000000..f0ae78619e --- /dev/null +++ b/files/opencs/run-openmw.svg @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scalable/editor-icons.svg b/files/opencs/scalable/editor-icons.svg index d41fdbde60..921c5f6d8d 100644 --- a/files/opencs/scalable/editor-icons.svg +++ b/files/opencs/scalable/editor-icons.svg @@ -8973,9 +8973,9 @@ height="100%" /> ;4DjL?#JK{<}`s9f!E^74Ncv2KxA?P znqS4>A5Mm5XZ+3RAsrteZUE@6ruIe*{@%QT2Z+HR#NdaTy6eD4apiOpRVR;q>U#s< z5dl4!@m)>rgBbjmf4MAT@Lz#jRoz?zw#1dInmQ0b+3!ohi5sB#Xd!|BF_B`hs`>b+ zZw+3k>gMJWZ!#-Y-K^MlYm70*n7>43YK=AEIdD4$-z`}kbRKvHY{lT;pH_`tk;1Jh z+E#V518gihk#XQpDcoa0(A;hJmDA@`6*Pm$IwA{8PjHzYIJPH+Y0*RP(g095rQkfOn@hH01mOI6nDCH0_U@xo^N`RX2O% zMq^$-6^6hEP3;bF3AoB6bo4Cn*|siYj4@_e`~+R?wcq}e9b^Ci002ovPDHLkV1jzv B=%)Yx diff --git a/files/opencs/scene-exterior-arrows.svg b/files/opencs/scene-exterior-arrows.svg new file mode 100644 index 0000000000..1f923c91bf --- /dev/null +++ b/files/opencs/scene-exterior-arrows.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-borders.png b/files/opencs/scene-exterior-borders.png deleted file mode 100644 index ec5040dc88138bb66b1aaabcd88783a2c365d44e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 429 zcmV;e0aE^nP)$3h|I3oKieQ2xAZ*62h3o`-Ct?@h&0sWquc{c$*OV%}M|O zA}3W^-2ag!@feN|5P-hSpP9G?L?Df*EnQ((7G!h|QaAGT4x9nF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-markers.png b/files/opencs/scene-exterior-markers.png deleted file mode 100644 index 6fffcbbcca32de0ce0ebee5fce5e48dda8bbbed2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 497 zcmVK@@=Df8_46t`^H?Z3BXZt&%2)_NV`VG) z3>H`H?6g)81e;X*0x2X!(9+U2AU2B1F08wg>RxhF#)SlOzHVS<&N*{t284KsdSr3E zUjG7ocrb@=zVCnj)5T8@27d><2MEKE`~6NnpGRxW^?Jp!EDXaS3`62LCYQ@`xm+*| z1Iw~r_W1Eg0-3a0S*=!?PNy7?N0d@5mrHEhMr%!@(V*Y&v)k<$4u|x5J(46zC2nd3 zY&IL#>ow2M&rBv0Mxzm77^0M7KA+=x9^>(tLZQH5Fd&mjC-hTV1=zMtx7%gE-@g{) zI1T`w=h1GrS*=!7DiunlQfiMlwGN!mXJ)e*+wGQOu}G~}BaY)YbF|iEv)NRCp;S7M zBni!ClXAICtJT8uJQj-uolb`!2mmlm6QvZUY5r5+=vUyA)*90^IUEkSu8Y2$&{j9<6;?)(17zjcKY0HqXB6ydrqFJs`%bukQs+wJyy@~OT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-0.png b/files/opencs/scene-exterior-status-0.png deleted file mode 100644 index 6fa47b4394f11ea2b334f8ce599bd5ad1315836e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1932 zcmV;72Xpv|P)vn>RZ-ZHAlsfOLT2;bBHbM##y@!CK3}zyN_j0KebQ@bED4c%1C)Z2J5A@%#M* z0)eqLGG+om&sHc`u3SlTb2GiYy(pz6dMT=OydNsARwd~rpi*x7Bp|z&1t&N(R8mgcxbRL%Xxy^0ve>d^3#U$-=VPr!DaFvx(74BA zu^4%Id0f7H8Nc73xXhr4{QVu3kWz|REJl8Q{`hC$8oBuWeg+2zlflo}iSzb2V1Zvf@@Wnt`` z5J@IbhElmn)dx^0Um7+Z>jVB74FyM10Dfla<6Y%|i8PdU0!yYMQoOf64OpgpX}LB& zl9=Y+hHK4*Xebz&j6eZOFU6YiVF!G`0l%6e@Rh)ifqwx3U;*$zq@nb0DH6$WM6$0X zqQRQe(NM5=G6H#MUkS#{1b7S34}4lgBCg;q0e%Gpf%TDw(vvquL~AgnD;f&+O-A5d z${%=-jSb(d$Ik$VfqGy!@U)v+4?F@q3Vaf{8+dTqMA9;{?a0;6XefAPG6HEpS~L_K zo(Av(P9{|%a>jZ5PT+@1se7!o06ztu5RrB_z7!B37uYZjA_0IzeCj5$N}|Wl0$YL4 z0&87=u}Et@cF7wAtkycmT8kso<1UfSKp6PkbR#ll9k>DTGN9FI`92Zpa31{u@FCz! zfCqdO*eoJl3BB`kz(L?R@DvaM9u|?fi^$JiMAlD-h?)lQ7lDh;5fE8mJ zcc6{{e-x2#yU)!)uZVopdHfLY25KA>OTa6@FM$t;$Vs>1ZlFj+)+O}|JwRMU7AJkb4|r2Vo^+q@0@{GD z0TsXockri-h^jpH)%UxK+u2^#bj9<$mz|#PPV~6js1Jw%7aW;qfQNxKB2u07>``E! zb3&tt+?h1~8z-9=fX7^V&2vT1KNb!5>}|lSK);B5urgeii&kY=&{%7gRw%8?D#LZR zpWa@w#-+GFd!F}Ux4;h1^SXe0MWjGPD%@fIEwBT4#q+#C!iLWS`$gnOB60|5aDWr- z*IMh{!0W(H5m_!Gg+LV$_dM^rZmmxMe*#+ZtB;H-hBBoTSUY|OZUYvpG_9~;v1lu|pawU3I((=OtF6_IZw zJ^wOrFVJK&)Q;2NDk(y#LgiD~5d#oyuWCA;){{`(yWPqPgMu5BU1S4`JT)_ zT-m9$t~17jL}XtQh}WD>pLGYlSwz0)#-0E^4y<>2{}@myBL7L4|7E4r9&7C(G+&5p zFU`IY@Kh`D^+bNx+mreGzWvTq4*^#l;IE6wfdoJet~&PthehQ331e$qhN%Yff&D-` zn$M>Se6qVxHc$-wBN__Y6r^>)T_YY4k)I_+q$%Ng(IO(-lg2k_ty_&TDS6!8(-s4l zqM=~_q}OSseI*tP#`FW7QxO5qE`7Y~ITw)!0l&L4>=BWt5+qdXYDxBVJnn!Op?!r4 zg*79mqM_jBsT&%lM*mVI;v&-Q^Z9Dy@%a5BayZH3>$KKCF~+=z=8@?D$C}a3Fok|# zAzHtCw2geb3mR?AM+O`bb0Z@8$1imUN2N6uk&J12+z%8fpMN3NV6C}U@z(T2L%}nX z6Bykv@zxh}|Hpc}!3i3j>mw3=t2^fxZ#@YF(L9#Q;VjF0pKXefAb zPF|}_K;#_Yn@(1_Ro)AY67gvTVB@n7{D1XkuBb9xS2+KUa^B``-sbI=wf_QPyj)Vl Sbo&wj0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-1.png b/files/opencs/scene-exterior-status-1.png deleted file mode 100644 index 2e1ed0f6503621aec50b177487c8c906a7e33c8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1912 zcmV-;2Z#8HP)J3 z{$a>(n{2#@$z@ihIp6O&=Q-y*&xJ7=lTnF`W^xb&JAt}U zVt7qeKYOKP?d>HJi4YElNu^R`G8sxsOX=z9Ash}9 zi9`n1I5rvprckb&HER~l&CPUmbs-|Ow6suCQi5}idGqGc(9poag9mA9YGV2F!v5^fMHn3*R8d9kgBEt6V+lj?utY5#L%F0Ssty+aKg;8BpC;}xV zB`jOEjKhZypC?mZUJgJk7GuehCG6R=hnX{HQd3h?uuWVr4|I2Tvt`Q`_U+rpv}x0r zJ$p8pOy+`joO6sHKfa(@s30D&*0N~PB4*5(!Q#b>iN#{<*s+79OPA8u*9SmxaWNu7 zadGiAl}29$<~ZjlE-t3Cvy+O73Y>F@2z`Bh=PjO2rztNl=k)2*gu~(NW!_NLyZ%=p zWC28kbUIB%MFoS;zy;wF4u?5=_G~Wrg6_jH`M;CeM7`7|>dn>EAy)}Fz!Z#Bi3l!Z zJAh*c>ejleN?^*S9+L6s=z)t=Ns+;&GyejRaHs_9t_qP{0@D$hkW+mCK|)2iOu8HR zdomvF&ja|;sZX@s0@(JIHLbwp;fPGx)KdgZlTgtFm&sBg@*Z4gPbA~f_Mr&G5Hl5L z&kuXxy&m|vJb}*wehmBri1@Dcf%cU(f6kN0I8UVXLLvs7J(7$^yM`iAjtNy`?P!2+ z0eXP@RJGj~yve|?fGDt_ePzv|D zsSUuRz+=FtfIEN(M@*z>T&e3n*P4t+&kRMN2q;R%qp1-9-{!-lPF0V3i{A$PP(gs+#d0`MLMVf{_rB5dePyIO#3E1o$*iB_iXzWe*u+Znf5)0D6Iis`{oUFvk;! z7-K4}wFiM)jWJ@aO?j{E_Fi29)aL6E$piTFKshjTFvdNoJ-{DS^*jE#8R$~gZ+VOF z0^R{m0k;E-RJAn-f@gurs(Qblw;q`7*SG$+8+_Ip)+17zs2|Sa%Y0aT26z#88Tcje zVO2fk6|Ms&s_Oil^Fk+(QPoK~zi$Tqrm9c*=i7nTfv*EIfQ5eJ59<-B-S@Q*`HI_m zplTfu*qI#}Rac=}caF<`8F$jV- z;2u?tsp<^BnSTp30xt(a5Xn;bJg`+&f269rfR!F_w*1-{vkrI*Sgoozt7?_MD9Z#v z@IAlQy})Zg3t{=#K*TUzM8LW8d*FItk`x)ify>yA>-L}ODASYMfJMNImk5uIK5p*} zg5bNpSbY_^1vsgyO_zB8ZJP{<9|}sZ|1!J5^xW&){T?KBj2u`h)9)$ z1lMl?k_YP69?#7}LlIf+i9DpLuLnVp0Co<>aF){JB2wv``+%zcC1>n|BJ!4V?j2RF z&KYY0<^wnT6x|NhKsV@$oZHm<6hb3nZ5_<$&i~i7zJdYFFp@-*s>G zmU;*{=K+61RkvjUTIs8EH?Uh(*JO>&^%>@FpaR$m9Ki5}e1Q+u3zY&>fWIc=QI|(( zZS!r!1FHJ79FMHcYA?2^>WZB4g~pf`Yi(W@_kG$V;8Zdm?HRJ2789y=IIy+{XdR9S zaBS)mZO?g+JP3q+%dk;ZpUw)QhkY$69f`#~@QIjEl^{6Ve>fSBo*w={BQo&GlZf|7 zb0`#gIFrfTud2IqEI!{D^Al_B3m6_932>YpI2q<~9;n2a8wZY&@124M4(9#6o`}61 zk&6ANI-&!iHRzFXBeZxJm?)ueCC=iUy)fdP?M%j_M~5abpfJ1ZOZ)G^v)$QC9CR-B zNaEd&vR}OW6cEMmcs`qR%BG&dKTTZLtzGt2UdZb|c~!1eEnVVRfsIMBkqa2el8Iv)&X5~Lbb6jeDWvBN50000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-2.png b/files/opencs/scene-exterior-status-2.png deleted file mode 100644 index 8ccd356aa985d5ac2accd33ca59e08b23ed6ad92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1969 zcmV;i2Tu5jP)=iJ`D(O=V)Qb1@3YoS$V4GcCV6ZfK9 z2Kk9rUK*A##Fq;LLpt4C9PK1_IyNoC7y{E4LWp$+L>}jKCi7(k#9T;*gBjk;^)hIA z5o*EK-QIKdN6*E}mC{>UCRzIY_xqjSIp_O*p6_{{^Za1Y2CZMF`ZL-0{Zb&epBT=H z$cL9ZIisJO2Y^g~&dyG{y1K~A%fnjB*|TTK&dw$n4AR-zNjx5B$dDnlx3?1v2FcFO zPOOpD9{_rwLOF5bL>e0#IdS3yN-3I~o5{`1#ac^MRTZ_hwd~!ym-_m8mMmFZdZ2$iKC%-HSowwZFTox~0%%MYv2#3S$+_{s2f&u`Fii(&&e?Hr`ZDYcO36zzU z4cH+bFbTD_wb9hn#I9Yt7&~?>Q>RWP9*NqqXU4<%uJM0WM*bwRcUlQ;8|>rKKN24q?95Si&0ouc>WoxkaOqGrGoFb69?`8+A6~} zsxn-oujC%;3IPWwL91e=6gF;JfTMdRHP|akpkz(Ej)pwbZ{SK5s|<~e#Xkj5!9Xs? zUJ)Xx1YSd_@26BBK%oK|*mz8U1<{aqG7aEI#yonk959i(vID^A-iVZ}X)gkvSAmS% zY`iNu&3z8nnm0*}u{x z@)e+21u`aINJN7*KaPgH)jbio3mw>uG5rDF1hfNph)Bd0ywSjGfCtQu)RpbKEFxNi zF?U2mp7caurV3`iXk(q1>+vJNR-hJG1w7@Z)&lnf4*|CWHv)I}nMg*~WZU)G_o5;1 zjh+Y$1%^jM-o8EnU+-ixNkon~k6#ZwtCYIQS_|+Z@R*3~apPkE0rG)4eIQZ+5Dj@B zq?Ja~@c2<+3GhRp%Jmn+wbqGC-Z{W(t@Et4I5Ms764?lZfgklXBE8muO8_4SG&?Qd zDkA%xNB;nP19$-Nfo}neMC4#n@4Nxn2F=j7toz_|zV>(?_wz{Y;11i&v zh)M(a3qS!dA)#>xY8&u(5&5NiZUjz<$j_a}w*ZHM)4&ZtwTK+>eg7F?w1~`b^PUE# zy7i5FH_BzLUL&F^!!^Bme6f?oUBFAg>%d=t$s)4PZMX^;DI&8{`h`{?E+W^Yyk84^ zC?b!$=No_>z{5ZVFvlJIUL&F^cm3pRuHqipGpXSd-}hg2dcHB)<8GriAO@UtWS$1@ z0j7({l$39G0&ATU>P6)Gl<_y5Y&HRpxb#}!ie4}g4fpK`;7y=iM7~}buE|HMaahn; zYn4_gt;SV`Yes#vtaQ3daqs%Rf3sU)x$pZ2fm=kRNJJ{!Vg4Ji9C+RL{p_R-UjWvL z$a5mH1*mg?lkKls>s7$}zzPu=FCxXjBp~kl{%_q{-vzb<%>>mqyA{JYr4(3u{tS!& zu2C6UVZp{t%h27YTk_=8dY~G3=_2x2?R0yS@B6=U#p*$z95^K+^%wbmKai)CT5hd< zNJO4;5&x%%{4C}BSAkoA2AidpfAovektkKH0t(w@0HS*)H5^aPLOl^#;fTx>ksZG8 zhk;EA4JU1STq!loT6=A3pX>YnSCvxlTWb%CNNLJgJunLx?^19h@DCLCMQY1(F9y7~ zI`9P|#cv*O&3WgO6^45vgoVVN7IZw?5K68M7DkAHX0M)tb+y-nFk>4kc zO?Me)3Q!2F1NNXL}XdY_#CZu zvoR(ukC&`zF9d!AG)F_;TRjn|QaZ57V!@cVf!lf`0vsLl=)nyxB6kBpcV$>DB2Oeq zXr8MjL;B)z2mIIQz=H~fHC;2JA#ZC>1Qww5b6C^e1u2y_5f_ohKp-$L9*@ruk*z5n zpQW|_lQCv9n)~|#9BaBe!|r~-kA}QgdLpn8tyg!qk)L-#-HmzIcHs7C$ouqCL<)DG zZt=RMm54}IA3Z(-s8fO9Laf1BbD`p`c`_RE9`1?2VxZI=%*1%28lQ4{-X0BkhkH%G z;g4JL{`~RdfQROhbPi|9ns)8ZR2(qCFklpr@Mu2pZZza=xFWw*E<)rO5a>%*xmx}U zbrT6_1z_U?5Bz`iXKrL=xTbjUALXD8+Mo^ERcrqR%`9J1x}cwm00000NkvXXu0mjf DE;gUX diff --git a/files/opencs/scene-exterior-status-2.svg b/files/opencs/scene-exterior-status-2.svg new file mode 100644 index 0000000000..2d1ab9bbf8 --- /dev/null +++ b/files/opencs/scene-exterior-status-2.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-3.png b/files/opencs/scene-exterior-status-3.png deleted file mode 100644 index 70fdc4111e7ef5edc5a6dcf03c356f7c5fd45212..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1946 zcmV;L2W9w)P)0oO{S+0uC?~BPAk&OWC8qkv(IU zxXVgl=*lLOh(&CVfs2$#j={C0J^_$$C?D%C3z2jJuOaf?wC)2463W4)S`?U_h(%6h z0Q~TP|mF`)!G52d`2Rb8gFJhj-*>h?S z{B7XAL@csDL*S!<=YW3#dH&RTM}1Z4-!mlgMW9YXIb+W!V!+uSC1Q~koe{Vb6WWBe zJpsNNXaa6m)q3CXh5)Yt5nx(smkkP9he0qcZ{nfU6R*$i8j>U+cqUjH({?7QY^N zMnrCM&H=m#Jf^C9ynYy$J@Sh8$h7Vdk!}Fr1f291p9OpmC=ro9-m?3QG1pmZPXK3t>8kp! zCosVi$TP+iTWj|M*BN8PTATD<+2Xyr04UGYBa#8|=Yc|CRC|njP}_jNsOm5LcP(&S zRe$C!z8N?KoC0nDW~l1^APAlYhN$W!KW`Z@-mh={zrnt0b?Ff)k5_bM@p(Qh?gU-} zUI+dNj8)Zre#51}AXS~3c3)@!QmT4&+W%F+KUDQ`|9u1S4)8Eg22A&f-=#;SeAkb^ z;ydpCJ!6)990b9uKAvyvXmP($BhUhz@MM+&_W%=B_2#s(JAqZ+2sNsDeVYCaA2u6- zM|^oL^i40^?hQY7An+#8q^e&nk5}}^$VeO*oO5CXF*33|UNQK?1tTW<61O7=f^mL< z>L3UX0Jo@WR8`A-GXEK<23`+>Ag{xQ&jV{z^;uQj3{-i*9pz8Pn5Dpbz#>(>MpaAv zLs=>ag5UVHz71>#>Ilo%+aiXMA_C5x%YlKw)skZb2QFog4%mI_Xn~$w3(NpsxWs)DPh^U!z7qsN9N5?%!yPs~CL+boxi6{e`)Rr_i^zM< zxkIWtB28BVOa-p-CAb#&E5d#Cm8JO?0^U_0_#BavH;*;+ef#4@#+V9gZA?{HrGa?Y z$LZ5P(Q8%pw_f)c@GW4PkN0l^<*NGc4)f=T$O`A&W(@b%FDUJIG2oeY;?EP=dc2|U zTa9bHrKSL%dcZ$X)wLY}Rr&7R2y9W+#T|4LeTBIhC<4|1doaw-6nJN6p>e>I!1zQg z(waeNt@X!bz>S&}bwAww=r<)cZ2vkWZJO^iS&YmCf z&OVulMIP>qz&v1tPt10GdpADm<9T}`7CF>q`Hg>cwBQdPJq|=LJd#Or4qe$~d`_kO zt6woN7-+X>e_%%<7FmB;o>eYD3yT@ gm0szUUb$lBKd<9oYUzj4#sB~S07*qoM6N<$g0j+%I{*Lx diff --git a/files/opencs/scene-exterior-status-3.svg b/files/opencs/scene-exterior-status-3.svg new file mode 100644 index 0000000000..401f19a5fa --- /dev/null +++ b/files/opencs/scene-exterior-status-3.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-4.png b/files/opencs/scene-exterior-status-4.png deleted file mode 100644 index 2f2b907fc843d8534d1128b17d2f312ece43f287..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1943 zcmV;I2Wa?-P) zSbh%KfCdv}&UqzNn{K{faMU(yhHona7X-20Vu+n)8}gXFNTPp8tSq-iJ6V*M&C9Kl z4#=ptO)uQubNr*{vdg8Vx3qP{^huiL-t#->e8120JEL^yd6)RSdPNy$>##+m)S+k}z3r$G`j4{;J)lpMZ!}|5> zDK9T)|Ni}K+O&zGp&D)t26L1CWM5}tG6gF+P15G{4+AdBjfyzAz;*s#=fh$$73N$vA?gmhP-v=>v zT8QKlcrQwoK^z{7CtIAGjKk$~}n! zV4?CAl-TrecA9$|t~F=lk#KA*0_7;Z0BbG{JKzHj_=P-y8{ikfzkm=h8(0-^8w9M@y4YHaBh%+Dk?lYf_`*aZGHxBX3h;iQ(`orm5jo~O z`ULP1;LCsqd>q&$A}6wX=br=nfjk7US$qz79(Wme5%`dZyyiCC3Ct6bH97r4ACMN28*{$j3%nsB zkGapc0$sq@ff`_)JNV;9MAddbWL?F*t7lo;OP=Sw;PiZ3w#VH@{Xhyh>&WZ^?g#D` zk>xqh9s%|`C$x&lEjiWZE}6Akz54Z!b#gou2&Hrn`Uv^sh_B=0?wc#_sQzG&+ z5jg-fIl$TWTdnm@;8o!JBC<$C<^aopwC8!>b8CGH_#@D1W*2;HL@^vvN`bXDa|X5m z&8k2Luwc_>d$2#%e8Y~|KA;YGeiV7^cDlXY^Stl4V)a#EF>p>qT1R>Q7*MQ~YO&Tn zEFzD)i2qGQzM1p<3&0&fTY7$=rT2lG+EA)q`4o28Sm0|t%i4aNn}x)5vxSx*Etbs%er24h{z*3 zhi~^gSYSFvsI-wAQ~g z#yn?(>cNQs$C{DOFpqwq0d)Vy61Yliv)+GP?}bK)_;dikrl%hG|LV=$*4k)e!^}I%8Jn>g do3ZQG{tH-lb!h)yw=Dnw002ovPDHLkV1g+Rt~3Au diff --git a/files/opencs/scene-exterior-status-4.svg b/files/opencs/scene-exterior-status-4.svg new file mode 100644 index 0000000000..aebc14b932 --- /dev/null +++ b/files/opencs/scene-exterior-status-4.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-5.png b/files/opencs/scene-exterior-status-5.png deleted file mode 100644 index b294c1b15a24c28c488c804da184b9402cbae4ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1924 zcmV-~2YdL5P)41!SOM2ni=rYE_%`Q7LDJm>p8=RD_}=eaO0<1!|Zu}ltvU0)h}G^LPha_wU#AImN0YXOqMQPN<1EC`}XZDU%s6F{(b-=kq9C} zBoeu-(&!(6dCoZ^kqA9KJycay;haN6=su+3(&I*@sILyhDC-cD4y0<6bKy@=ql zb}i7je@^S^OG;qsrW9R?*w}%K)JuuMWwQGKBpmuM)?E@J`2^mF$i%$r0|*i-!DTbO zz(2bZv4IhQ{~35-defm4U^^RYD}f(NIP{?l5t+IvRRYYAP{~A>%~2upE?j4ibtPh* z!x4xhW;)KE8TP>YJn&Nm0=K{~fPVo|-?h%~Y^<#)kVvT~QhqiO1I`}lO2m#1N1zfD zs=?Z^0N)CvfX}IFr!RO@fER!mu&A@Kw)~=q7z5TG>Pp0Vha>PF2}iGSne@e4yc^gB ztOnKtkNT;rf%||5fzJXr0(Xv@NJ(kA8#r~aD-rwma0E(#lCDH7JqqAY`7oKIs@>k= z*8^KbMQdu5mR>O(+Xp&pR}fIkgX0<(r<+=JQ!{83fE>z~_! zqpJEHZ}FYLo4`B34ZsprJs1SRVEy~EFI1Lpemt^e)4K5LEW5vfZyw2ff#6+SFJ z4?GLJ2s{sbL{(q+3fBXZRdr$Bd7%f$s_NBwzi$Tqp{kGg=No{Rfo}pcfyI8~kLVGp z+xLL;75BFNb6Q^rg5WtH&o|~;+$-t@GQcrUrVY3UxLs9m$$NJ%u-O}-MOClQ8{h52 zW(Tm!r`Jkf^uj~l@b6v){2oZD>PPF64WGftZX6h#J1s^KBhS|*8y@Lwtex*u+$%v4 z-0T-<4uaqiaI31uRduG{%)bMgffs`yh~_AK8hBDwf2OKCfkqEFSAJ`ZSr5Dh{7_Y| zRnQr44hu70As#Te6I ztxc%v<~$Iu`Z#^uZ}fIm{gEGg82A*h$jAF9fjU(^oiqPyBC^prx6_r%y+>BoG+hXI zp_TYtB5$Xgt2cK4)LZH<;FJgaEmeIg2T-H0&b`1cRb7`ecDv6ow*XbZlfZsgDqk)X z_;9^YIWQIYdsian3J9$&zKxi#s=vLLZQ2}+3X#vx+~A(3ym?qwAMc3O69)M0LR(ElVJhpfohDI zHh7GD_Y^dEFdyjiL~MbfHRO@fQCd6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-6.png b/files/opencs/scene-exterior-status-6.png deleted file mode 100644 index 872568b266adfe23d89f24a761119fb1d6a27110..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1980 zcmV;t2SfOYP)=iJ`gOWWHtrC2uU2x|kK&N^7xw3)ac zxJAq>i2P_+!VuI8YaOY&jfF{VV<+3hBFuCkwi-jMGa%zJiX+)i0%9)9hJ%<2m~z=j z1u~k#TDiUF?2n#{mn)^Ww4G$>^WXcP_nhRX$l%ln@m8`5RthLOYJC`+U)^O<1AsQMQsIIQY7&9#U z`i3%j%9JT%6CP}DZ>O-ZkXf^4v3T)f)~{dB$&)A1TC;E8J}N6Ksi>%+sHljQD_4@6 zo10iB*4EZ``qProMa#;{qN=KjqeqVtiA31He?NJ7c>oj?6tHB;687xb!_=u$DJdx# zwnIE@66);iq@|^W0|yQ;Y0@NS%$Pwu9>3%pYb|5Oj2YG}G%OJ?#!y*VNoi>*ixw@S zprC-QTeq@e#R|H+y8#FU0w|>j1OnGo8l4P;thEFJ0XjN5$j{HmT8mPO?(Xi39*@Oh zm10J4Occcq2?X@L&z&hpked$U>iZ*o> z052e*~7H^cJkSIP8G`8Mr?h4!xfya09#y{1XTQV}UvC zwIw-e68SvPs(cyKFD0VEn(s%$p{D)_+=KRQ$C#l2Zvnc1yG5kk6}$<+E+7OfXs<2F zzA7SGgE4nU!y)O9z&zy-zG`DVSL^W;z)oNdumO15O}T=LEXR%@MOt;Lb)aF@ttAOd`Eun`%s4qO3v zC(!D&e7lIWIgdUAd=+>Q@PMxaD@5c-Qt$i{uod_#uoh?s?h}!?i^%IPA`1pXL=6J? zcHoTj_!8hw;5wyLrt|D!t@RDYnA5;HV4;Y-;|SdD2n4m(d${5q*qO#LP z^)a9<-H50(fWHFd0aFticcAtFe-V+NxaVfzl!*M;d3*ujRSbR%E{s$;5Fba;7wq!fYmO&=DDKhPej9gJ0AE0&?O>YDT~z1 zM62Cc&{%sxX@%12&9X?%lkK%7b6kph+w;6z-2(NV=N$oV6OjTDDRqbW_dq@HmgjlF zqzzvIUJ#MzMPvt1>i{R)@3ht%fOmmkh{$9Sxeh1?;-2UI+^zL3U@y>W#%6r2PciIP zN`bXDaRycabt*##uwdh6ZDwbzZv6W8EkGsk+GXU?TeOhk%P#u|Y6z+{(#n}NUE zOm+Y9RmG2A4){QI;1fi$I(q8I{rW_M*1EHQl(nTT9Sn!j8r)nu*RVKddTYO8iM8ANJIbYdZ0q^VB@xBM110BJyGq zpjua*JAs`d@>J5;T`t2^0QtZRz(Jd-mZl55zq`<_z&c#y;16rfu&>#CFFjr~cHj4#g{shb%hzL+SvF=E- zi^#oz-(4A+L}YD}gciG6l06uYJK&F_eGe%V*7VMfhC@61Bd`>upU0ZME=Z}giMWVt z^7(v=2+$HxP;%I9B>HCSsdRlGIpqT$e^{Sl}Firv9Xj3=t`8K>vH z(QxS4K!-he_Cj6ZGso+Jkj+%9(>a_)o4T|+Q*pom + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-exterior-status-7.png b/files/opencs/scene-exterior-status-7.png deleted file mode 100644 index c19431025c42cbe98696a531473daf375240d7a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1957 zcmV;W2U_@vP)&$+#~m(pvRaw}|t#L>7;XB`YSF%$NJ zTa5V$B5z}fLr^b_I#YEU3zOPnC!1g=#dIJR#SrTZ$asw6NH(_-1XGp`2QdmPRW5jYbKF!=zFv(&;oqhYqE+wUux!`+wJt*R6MS0Q8pM1=PC zb_xp%>G}rF372p<%<0poGr3~he{jEhg`}%`bQUasawwh!jX8R3Xq)c)Qu06dMK*FIfW8Ebol1X4YBHzjAK7b&h99+6xf%(Zq z?07H0{|Y=by7FKvV4Et63xHJ;4t?oDL`JP`jRP-9C})aGcXUH3pTKqY*<>Pie@_G! zAZ9(zo>hC`e**4JCSo6C3EToN0sjP|{?t0HsiHVPOCp~G8YGl6;anmHoc%#E5v%Ko zz@3=T7Od?L@C`sKaEGck`Gz+V*bc;iSxptiLobSmF<|W-$wW+hA}~Y3(N|o1>S8T^ z6xaqV16Bi1`Kil*`+$dmTY(#ayZTHdCwGGD`1sq&L~KV-1cm{_lZn`YJ^+8ghsk(V zJ?brfJ+NLxZgS26yaGI~s{6ctG@!s0!0bK{DFH|(V((>DMzgT^hd>$dJzzQz0)}^; z^{ut10cVWKch2F-H2dR2J;`z&dY)YE`{H zL%+j^&1PV!FRul@>4m$z;m3{u{s6S9>Q_n|%co#u2M!F*oe?95kvB>k%b#efD4ym^ z-0mO zzf{$0RrN~$P?ipY;1_6p4iN$8TvrY(0V*ZOL~!8Jc13PWd*z5< zHLVBc0I!}WJnDSh-V_AE&wR6b0Qfv`LRG8JGyZ)bUqq^$a}TTPQ{Ll$SJfY9jDH=t z1*l1nj8q+6aCHqLWfBrxhjqXY_l>VvlUap&B2w*%%uv<0gCJ-GHh0Bvw@p71ks{~Z z7gY7V4BZz+N2#i!x}(o}OU(d2_JDt+sxNc{RN=dG3$RU9S9a6g?kmjAKq2rvu+Qbn z{A_{ubQYQjtO6z_6S0mgLTkN0Mod%H-)4BErrYsigQ`B7p`UGxX|UF2W${sKTML1o z0}aVU?5&;%OcxW{>~LW1AAu>o5dkVjS01eQ9=Qt$`y)f0s;=l3LU;RKGPExi_rM>+ zgdPwCXFH}Q6R~YQ5txsd=Ww?379^525$}<;p-|}VbUHm%RkvkWe5NtxH`dxME?4gB z3vit6ycu@h2ZCfG_F7K_7Gg|Y=QZ+^TTti4yki$|Ycdi0cb1{m<&oSzT6_dhA))X> zoW(hNZp1sgDw&8q(i4Hjz!;yHUHYzWe8R``u4E#1xVL1Fo;*`I{OKcAK+NUJ(rl7* z)Y?|#b1Lm${fdCAfG&$(0qjmDVlQ5jXO;61sRr^9c`)0ma=E+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-fog.png b/files/opencs/scene-view-fog.png deleted file mode 100644 index 65ea108ac54205654d7fff77793bd02a80e49274..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DWND0s)y z#W5s<_3bQ2zGed+7U?7Z>+guMU!A)6KqlYWu-V)kC#TtN-hSa;q1>T#;dy^$YR|Jq z&8Z2ma4VQ+`21V!0{*o^%lY3J+x{0`C^b9u@sy1#H?NYM@ocW$mA4}I_8wTTd$WPp zfvHb`rQ>uigK`UR4g=GXr)CX;huF**7#|f$AK*x2mTq7u+{v5JvZ0aJp<%}zHUp;{ z2iO!GV)B?J6mt@o1r+qQGxi9WZD8aOn9l$G$W7fm=J=0#g&%9h6+0hVe>iwx)%U|Q tdse4w?-&2FKP=_Mb0bryqQl=+`Cbc3rY~%JH6Q3t22WQ%mvv4FO#nbqZubBH diff --git a/files/opencs/scene-view-instance.png b/files/opencs/scene-view-instance.png deleted file mode 100644 index 6f5e7cb2ac6c688073347515a1b874df3cbb33cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1774 zcmVe5m|JWVRUF2@GdsIGyR*C9ZttyKE|IpO7D1?h7TR(X zx+N&YHzht1HAG`H#>Hfs_OPUkBK|lD=Yt%Rt}G z78rX#&)^isKspTqYpHX)yY7_hUlXu$gKMFVv8}R>xv~tpB8O3gMaQ6LDuzQ*1mZwc zQPCZy6P2#{Lofc;cyQ=KNE3Qr({5y1dewxDQs0kSgE zVASg&84U=A!kiu9XcWBzL-6_oXllOj;@y$9XB1IVNtW$6Vl#$XTyTna?d7f}#r0^%x{g=g#C z?hhu`$XydqwyuU{io(t!S-*W*wu}(Dczh^~B}*2dcve0{oeqN@FV1!LqNU{mW=jkO zHVJ}2u>L5z3D_5n!yy6`Gpa4nK86<|d>@rIN$>Qxb*|dQB4b}vWy|vlEqXK$1>t;PKDMsPrnJ~Bl?Cs_-tYeUO$5Ep%6%qO?ytKi&8zTHm5?ZC(0!s~eo+ZtDG1!eV+tH7#LD8KEZ zjOt~J60Y}N8OF!;hwF0^8l zT3KDsC@Rtfn5YleQgOzosOQ6KUeg|T-_1VGuemmEY@$QwmDu%-bF%vV3xP?}{$L$43%j7C!GWs-Xm?*2)yGV;lH zyY16)bgIu>orYg~jNlK}P;7EcBK8l2QCnFKQPiQ4!*_LJKEZ$AIf6VmK|U3=rqo?Rz^<}~kCW}2%DWIgs$qkP&`nV5wAa_TIi96r;69|*q01lX<)Ov!lM z{lEQj5A`@-Ctsh*^Kk*>{IU|O;gynf35_&g*|K3pqQUa{42Lh}@Eu9;6R7wI%iF*} zO#Nm;(Tx5aZ^Sgh9$t|nqq*OQvWLpxuvzg}_hp=KZl&JFfd&#)Y`*Gz64LkpME!VM zNcpmURDC|Fc*ai?0%%tGUPYGK5TN?s!*N)@kKuzsN7K%i6 z72lV$O{ErDDJy6l4na~Ppnr8tIv>|wbgz!%0yKa{k5;i9yJgpXX8pF2NF0SmhO>bi z7ZTe2_xgB)01b%O2pRt>tpY!!etmoeq?MR07*qoM6N<$f~KHTxBvhE diff --git a/files/opencs/scene-view-instance.svg b/files/opencs/scene-view-instance.svg new file mode 100644 index 0000000000..771432bebf --- /dev/null +++ b/files/opencs/scene-view-instance.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-pathgrid.png b/files/opencs/scene-view-pathgrid.png deleted file mode 100644 index edb350b8b5842e835fd05cb269890e896f6ada8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 569 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DX&fr0V0 zr;B4q2J73Yw%*K+0&V5yw^;tL3LQDN-aa8QHCcJdrp+5frkDiQHz_zt*>_FZwrFZX zwz}9xe<{b_)(PhvM2ffXD?XWXZu5r+3B^Y_p1(PFCiQ-8S(%^aPhln}1>we^zmHkY z2Q8C7`C-$^4|@A|H6PUVuet3b&G2*9rDY4ag^eYq*LE-eS~AO3F;B44fBo&?_&NOd zZCcE8FUapOiMVk=;doIW|LkupdWvcGiZ5=kH5NZq$k_8rW**!Bc?=%<&0m^%6|db= z;`r_%{pa$j8QZv=r`>X1b3!=Y>@4S<9Y%q#x>_CINFSO1m`@{T!kyPLpEK`rHoRaI z%X7FP%iXY^?NsQpHQbX=vEA1`u~YtON#N>oNCI*WBW`TX0bMhPcgULq^@TJ)2HV zt!6uorW2?cu#X*iAq&D*QJv}JJv7{`b z)k3KM*eZYFL)TW66f~aPJF}y=)@%9?f9oU5wna#G*e`x3zp>f2=Wcn!|x{ B>*N3c diff --git a/files/opencs/scene-view-pathgrid.svg b/files/opencs/scene-view-pathgrid.svg new file mode 100644 index 0000000000..b1a0c8845e --- /dev/null +++ b/files/opencs/scene-view-pathgrid.svg @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-0.png b/files/opencs/scene-view-status-0.png deleted file mode 100644 index 1906fd89cd46cca2821b24049ba5e9312ad6cdd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3547 zcmV<14J7i3P)e5SP5`cug9Kkj}3aQsSn5Km9D%`uz1gN7w=qK6Yf| zx;#L@UjiqSb8tBzVdqD`WL@+eS5$t5)@Z}9VjAC7~YB7(PtaGf^3QyXznFqIZC{sP~qgtr8GBJ0!T(YNnQaNSWV}L zrTGy@w!Uh_^?&_zWFh;@pN|Y;KFUNg>?lxTW~w6CRuJRW^m00r3T2rc#Pq6(7Gv{8g@tbw&d*+_CX&8D8zicfF_R^Yp7|ktiMuq`Sp=|NxC1E zF?*c6ko$p$$zbSN+RXO2exw0OBpu7peL+J81XJnA#qTXD+87rZwzgnqHlm1iKV_)S zo?aY1cNqhH{m2f}KrIufYJ1TqQ$f(_kv=&AQ4vO{l}c#TD!2y;2PI*T&xh8|9ynYB z_;7E@`U_>}MMK6lU{i@(_)`^2hePR`1tfv}1p)o11dzPIB(ja>UY6#hBa=$e{F!SR z<1!hC0b+COu7gkocuWu1AdH-8b&%>z;fPC2fH^i22Avkpfk8C1bfC)8jHcF3L@GH% zDJdZkfhvUzEdxHZdwB%N^8sEEnwo5D%g&$Q#P%KK0jFCKroKnbj4&k6WLoIx>Tf*2 zULXTv=+H$5FiXXe|HPKhKezuOuMJ@tnV8!2P)Xg-VAOT?DeI$}z`lC`RLxb~DV3^-_K6UE8kT>E? zq7IqC;wYr7QjnjuhGmg?xl;!t!VH=KF;`yQfcmOB%ptkh2RyK-jaW202O%0Y+9`P7 z-+LT$!_|lnRpVl}8_g~+vh*^9P)snEM`M+CBiR^@g4M%ukui~|t*(DRQ^SY&1#WrM zpn%!SAH=FvD?u4}w1n8}qXL62Mh2&NpZ+)~@Q#$0!|8Slx5#PKrgJa{!eMO^7h) zkeV1LM$e_1E2y|si-mDIRChTMnmz@&>63{`0mn(_j)n%ziVs5t#et=N5ZR$JD2PP{ z#Y*nqle!)cB*=sXk@cK{3*h|4N>p0gkUKRskdZt|*5hz-jcuKn6djI8lTlokUgfPV z9h|kblgpo$20h8s-tWThqo?29ymi~7%s}po-MfX3nldECM~N><*2~FiR!C|a+iGuY=Q> zXg9H%V}a!}VV<#uOG_Gx(cqq9s_hr6wnnKCy84{lty5E>57B_doiv!d`bo~}xHQNk zLbcE9G)hDmLwUJe&Voa9F>{;U;q)9TEmx$I400)x94A@KT`(8sxF|7DH-CNzOJlXv z;BffJ>c9*tA0`#+orcCmCyzq~Z-dhshG&NESRV1^wYE0w+_N9ttAG5hK;U6%Zik*KWOiCI)4Kmc zjaAXv+lNdgF&d)8PMZS{7u^Y+R)g|d3(C)4#GU3a)KNaK>~$m0AQug15Lx}P4Dk4P z{$_AW%`o_@53K5Q)WWP&RSA0gDRi{fa5IQ;R;d}|L@2tb_%%|co*W;IuuvV&P!VjS zc$=1*i29~>C|qt2B1!8>agm~ni%*<|f`-fO7QshrOH&Q8E(qPV#naTFSSAXXdc}ioNrOFO%a8*%0)86!5)>ak-Er?}UtUzAQur zWr_;h+bG^kUc>z9DTt2@6SEYhQz-6OvK-sCzKfjXI2iOH@KF!bVedk2h+LGzS)UpY z7tVuHp+L#`%kVc^Q9$4ixAwzC!8nOhq@;HSN@Fyls@IF@dL5or*W$0wq~WbX1due`Y1-iA)25SCLd2+yatQ62;2NC}oU`foJc-g&GS|sKu7c zIKe`B+@1bQY(pB^K;Ipb+ay0s0DL;dLs;Wpv{)}}IC^mZE0S8IqRUcqwI@a2%#4ES55oahUmI4gm}DGTC!1{mS^^ip_E~B_Y49 zy-RVWxkIedkw!h(_kxoTL$xmzA9&NyDP!F%X@9I#M8>SSk zu^TNeF2)_vI@JL6;|@bO78CeBrwa!TpTvq=B2n7mf}R34j6OJ|fXz-xfKjKx!INdO zm`D?{Qq4RiFP4s3u-YP$=Wp+l*X<#^WC+D$RY zq+HCHr=PdE)2^y(>OkRwStLC@HFm?FsKdLnEY?GxmYa7TK}KSfKp#2Wi|%{ipwG)m zgWX8)H~?EinJATs08i+F)sL>mr(3r`VK6{PW!plo&rtZGpv8|8`wL9sf87wEsI`*r z&$QXM%WMuyXP@)Ayo_WDbhRoz(ln^2moL^dAe0Kxut&sBErWO)_ZyY2q`j3h-; zYy?jR$)~vcp|>7Do2~rm_eV%&A_K;o55@qMXxWY(pYR8Ee+q$gJYUD-vV9O5-U}7c z&iZi%{9JkYlkUFW9%-?~cQ5%vQjF>O>1k&Ab_KB?!aU-!wfCr5U19A&3w@wxCMSp= z?#&%`iVG#mY8r)_mab=xA3E@gwAObW$MZnYfRT}X+q~P~&Q6Gayw4jzqMF00)~kV! z-Rz7{Op`*w}>8~%|I=-KusYR6ERdd9TW@4F4XW{eXb1$_U#P@=h(F-y1r%u zm@#bR<>mdRvbOH;t`Uq+w74|?z^aumYE+7Kv$Ik)ZjYCzWn^lKsk4<0-PmjzHN + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-1.png b/files/opencs/scene-view-status-1.png deleted file mode 100644 index 6f5e7cb2ac6c688073347515a1b874df3cbb33cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1774 zcmVe5m|JWVRUF2@GdsIGyR*C9ZttyKE|IpO7D1?h7TR(X zx+N&YHzht1HAG`H#>Hfs_OPUkBK|lD=Yt%Rt}G z78rX#&)^isKspTqYpHX)yY7_hUlXu$gKMFVv8}R>xv~tpB8O3gMaQ6LDuzQ*1mZwc zQPCZy6P2#{Lofc;cyQ=KNE3Qr({5y1dewxDQs0kSgE zVASg&84U=A!kiu9XcWBzL-6_oXllOj;@y$9XB1IVNtW$6Vl#$XTyTna?d7f}#r0^%x{g=g#C z?hhu`$XydqwyuU{io(t!S-*W*wu}(Dczh^~B}*2dcve0{oeqN@FV1!LqNU{mW=jkO zHVJ}2u>L5z3D_5n!yy6`Gpa4nK86<|d>@rIN$>Qxb*|dQB4b}vWy|vlEqXK$1>t;PKDMsPrnJ~Bl?Cs_-tYeUO$5Ep%6%qO?ytKi&8zTHm5?ZC(0!s~eo+ZtDG1!eV+tH7#LD8KEZ zjOt~J60Y}N8OF!;hwF0^8l zT3KDsC@Rtfn5YleQgOzosOQ6KUeg|T-_1VGuemmEY@$QwmDu%-bF%vV3xP?}{$L$43%j7C!GWs-Xm?*2)yGV;lH zyY16)bgIu>orYg~jNlK}P;7EcBK8l2QCnFKQPiQ4!*_LJKEZ$AIf6VmK|U3=rqo?Rz^<}~kCW}2%DWIgs$qkP&`nV5wAa_TIi96r;69|*q01lX<)Ov!lM z{lEQj5A`@-Ctsh*^Kk*>{IU|O;gynf35_&g*|K3pqQUa{42Lh}@Eu9;6R7wI%iF*} zO#Nm;(Tx5aZ^Sgh9$t|nqq*OQvWLpxuvzg}_hp=KZl&JFfd&#)Y`*Gz64LkpME!VM zNcpmURDC|Fc*ai?0%%tGUPYGK5TN?s!*N)@kKuzsN7K%i6 z72lV$O{ErDDJy6l4na~Ppnr8tIv>|wbgz!%0yKa{k5;i9yJgpXX8pF2NF0SmhO>bi z7ZTe2_xgB)01b%O2pRt>tpY!!etmoeq?MR07*qoM6N<$f~KHTxBvhE diff --git a/files/opencs/scene-view-status-1.svg b/files/opencs/scene-view-status-1.svg new file mode 100644 index 0000000000..3dbfa1622a --- /dev/null +++ b/files/opencs/scene-view-status-1.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-10.png b/files/opencs/scene-view-status-10.png deleted file mode 100644 index 1216c180fe3976e85e0377406721156f179de0fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 777 zcmV+k1NQuhP)e5S-)!(K@@&FcL5352o@qSf?yD96|7Xm!hnU^rO+-G zrqcWkijcy>Lb0(`Q>Cy7qNO4M5sO?S{;mWCiD$gq9ly7GZ=BszJo32x0W;k3-n^MN z`@QeInO$}xi$)D@roq@#=xqrgLKD3S5`_~3r*Q!==%$p@fb6t-)Frg?vPa*3RG2en zottDp^id&A-TvOk|Lv&rHS>Ti7JMwZsWJfIOB9(_)M`&dKcfLRqVEvih@)Un z7X-Ki=)d_ezkO5eu+0GYx{dppDZ<(|F_<&9W7)?i`cOmyupsZFf3;w{VPifBvW7&k za(vJ&P2^aYs0O<{1p%gyfazy80a$A%!El!cVBbx~e5521uw9HzN@pC>$0zzI8CY>; zLiA980qiG$zlA+O2$0RQGFGN^0f5oP_g@^FDF)bKKP4S5OtK5GUMxD6)lc`pqgOqef8N7MWN7)s1RdJf zc8?WcyaO=%xm0k!0JX-AT0(GgfZAPNof>g-#yKYW&=egl2XPojTgr<$0B!|~S3+BHuihsFP<0MM20{ju41^L=5~y|$8BkNG zTp(mXNub(2WI#=!a)FQmC4p-9kO4J?$^}9Olmx2X8=ryPcVeLfuP^vR!JqeyGEhVL zuM9K-JoA~$fd6f*hGhR0l6y0UQGRLK80IA!zl0hGo`d)e^!6c1cMT8=00000NkvXX Hu0mjfK>uAc diff --git a/files/opencs/scene-view-status-10.svg b/files/opencs/scene-view-status-10.svg new file mode 100644 index 0000000000..8263818403 --- /dev/null +++ b/files/opencs/scene-view-status-10.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-11.png b/files/opencs/scene-view-status-11.png deleted file mode 100644 index dbe8276f07bb8180ff786c28c5db6b5e134a52ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2424 zcmV-;35WKHP)e5SZiz)*A@O|cJ}Qxe&Bb#CTU|kI3`ZPfgr#b3@u(e z;SqjRO;HpTQdLg#qixhSQl(a?tturys4Clx23>(~z1iKSj?ckaD&=icvp=R4=#8BU^td0hB;j9rEtk|bJr4T{4|77?^ozE#yTsC2bB7BA?4S*!u0E!?)bYIN7y0? zK6Yj0t~@}%UjoPLIGs>H^~di9+%%uP124y{U=K50Y3tev3EYF!{=DZ|TR`cjz9IsC zNs5G{T^!9Vi9C&&wY03or_}GTNC6mk{U^157T5PjDZ&y#w2&iMrEhTj4?N!sQB=VC=$=Js%*IuVPpMDG1C6RofVUN31oj4j z|1-q^$UxTZQ0~p;RW*PZ{dxDG%BGBg`d%WIGvo$^3a8UoQ{PGjAQ@3dUI7`{N%a<` ze=e`ujqDGelK_4g-zKskJqPu73h?3sFo=m~n69k*ta zqfEzfJFGg+kzvpkrWpk!={V>)2_yaxycA*~Q9^G(jx~&U{Li0iJ+?6)tVt+bl!uJ;1u*LMFc}T-`3VOl;Xo*azJXylJzli6UwQt}fdjH32@R+};gfb> zV(BoNzF9yL*ry5T-xN^f1tyUAc$>gB8qa<%sTP(B(;r>nxz8Ay}35i`aph? z9__oOHF;JtncE4P0iTQ+Zo!C+8v zPgE!}v{4>2i>C1~X+2%wTfUQ>RADF9KkWCQ?9nnjke`iEID)1RK89}KHkM{5qhrK} zYpwtm6Wi8t;EYLDrZPec{IXAcb2x&!lLs4y*t0P~tOc#Dvw`uY%2Bh5w0hi;XVZzK z-!8$5$`y~utoIF$;I9WxV0%Fd!bES&EjR2&AlDR?WPn)g3rHBGKx8&#QnJW5a}*=7 zt0V$`>QLh_8>Pjk*44@s9@yZh`Q`mq!|y6{tmqtc!&X>?vcg4dQnZoIo!746(cBbt zP#ko*{3x~Yq*am&EqtBaf6gPm5CZnE(R;Ux;C?$K=?xxEk7dbhr>B=&jZ_rI72pg! zD+#eC>?C8Nk3|31J8J$?VlzIyGRuN9w_GSLe}p8HgD`nvU(<0cF-fF#6W;4}B10!4 z*UYQTo^?jVo*)*t9&BVgx>|mY9C( znU<#t7Z}k-vRJeBF=VBupnqr-ZyavJ)_fb>6lF~}+*q}MhnX_4qKfk_QMw$Ea@r0y z_A4Obua4SUmJYiI!>EgAJbr9KtO+~E4It)Q9XUy>b1ix_^f>X<6YF6$o6vrx3+-pm zVZA*CS1F%g9Q9#ovQE~vB8M)oi2u06>~n{kCIuf4aH@79tnxmP=s^X1rb&mx{XXp2 zQU$ZoD1$#w;H&JZ_-Md`U@(O81v*(W3iEE-H9Nf0Pwyr8L>O6LOzKoDtg5MfpD~|m z;L#p2!Leo;^n!pBpIkt=`#MT8Y-s6oA(f+@l(LLAldxd?*eQ~89C!xICvxZtNV~o) z$eBJDV?guKqZs_}G2~hGI7C7D14o5?2z&pNiwH6B3zP6JfiE@#$>ST7c0tDe=bp$Y zWgPYqU`~o^0BL^3JFo3UajFTebY9uEX|)`%>^#H3moV_xHQ?jmvBb+_U?430EAG~; zHq{`e9rncPEGyb?yHNV=Qlur@@LBIITyDQW*(MDwWKBpMevtE@AIMUpVRT&n8m(JtP35VX@wlkTefb+_v|(__l#oO6B}SM=uWs~eN9UTanDKa0)O_lw zeVpI5^M~Sl@9u|05m{jsabd&{lNbbj>X6UlbIo`>*hC&s4bW~5n`&xb`G!TmYb+Q+ zzLCR4Z}dDq)mbHe!F%1FwgJqT>Y7@Ph<}0J0$-)PE}zHmHK3W!HzNaPI{93TPh^0) zk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-12.png b/files/opencs/scene-view-status-12.png deleted file mode 100644 index ba6d1f3235bea33a17748c01fd6c16f9df53c5b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 923 zcmV;M17!S(P)e5SkH^oKop)yva+Ci7k?po76lhCdhieMq8AnM>fZF` z!HT;pD0E(BMOa)Mi?N5$JxxHO=(Y{t5RMO=PC|e{+ajmf*`a#=9|@VV`B&wnWn*Hs zEdo7wdkiK!8`>3tX-BDwKox<49;hT#(1C@gTOWbcWo{IA99;Q%8snd%K0$rBy)gG{ zl=#YgSoqqX0?d?;;X4%mW7^g7P;aw;`*0k#RhqCcdPdG%`x4 z8%jdNovA6mopgg3IQgGoxC|m|Nk@SOicy%E1ax}H#e=N%qT zf=Fl7_nPBbtgJBy0lg52Jd%!gLj?v__(MWkAoc*`mh)yT7AtEkK!AvV#47f}xny5x zcH#aA_sRv3c%jW%zgeuTu^<5g1QLD!Z;EBLg<%&#K)Vr%+R5x+-Y>^n=xQe6jJjctte}Om{JsxP1KjDNNdO^jyo4LzMa{=zW+R> z7YHQ5o%<@00?_|~qHwEcFLaN`8KV6791&t3-Se1ti8g-FY6$;(U5HLg1ZtmfyAouX z5)#mN4d6eAUhD=v=$SIs?}ljad=#B8n?kj5&7yS7?5a`;#2Al57Y%rH5ag(jfVY|e zy&*bZw}gV%rMHXv*3mxZxxL3zq9s#T)E>UtXd|#6R-`*~Ej%B(T^W1a|51;;jpsO1 ztz(s|JIklu(`szSWQ`>dAOIjir}Y)on*g|51<*dEG7LD)rNUxmjU^F?U__sXp0*CR z>mN2C8IR<}dE6XNZ5AtQoB#m=w0H4!6ToM2E~KPFeO%$NmYqg99H#9pz%OWxn;lu( z6WhdD0Sy|*MIb|N8}!QQ_f-U@9fK+YRRjupz2O07$UCzgTp)2TYc6z@ESOK0G xr6PbUkTt+{IFhRb4D@@)Is&GItOHL`_ybdd-_5S~u(1FD002ovPDHLkV1gW>mmL5A diff --git a/files/opencs/scene-view-status-12.svg b/files/opencs/scene-view-status-12.svg new file mode 100644 index 0000000000..2e16eb8256 --- /dev/null +++ b/files/opencs/scene-view-status-12.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-13.png b/files/opencs/scene-view-status-13.png deleted file mode 100644 index 3651cd609417d6b77e862e22d97af1f2fa9f45dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2287 zcmVe5n0stgSsll}_c5=|qc2+8LI*V1Hb8-8DKCMxP*6I> zg>E(*bs_#CU<`2+O^k}}N{lA{p;0zbffbD#-HmaDZOe9dODL2o3(G^h6qcn6ebM)G zrZdxd+kc!>9*2EBE&q5i~xPdiFaBJgQP z;DJQ;IAJz!cNuBERG#CO8aOy3QfM~vTzy4(dCO#3I4%3Ky;RMPw`sd;g}`xp ztODmsF$!5pCJ{D)gMm}fKOBOO4hA7vL2H02biVBO4!?M@=FDOJ_ zt_^m9`(=h%%*#zM!^A5X@C7l*8=+Wj$XYlLDar9L84NI+jPMT!7&`)?5Zb%@;P(1Z zTX*}#%2TIQLt?VVY$Rj!mQStz87{wg$>9ZN@xbyVGZI7&P3~bJF$w9J^WaRI1Bcau zA>T0Wwsqm7#(QXO@5UU`QHp^Fo7yPyX!C{88I&P$JS2i7hHq4ql^tO$)aSlhrfWdv z5zpsotiRLs8@hg;@aFS@IU;{J%W62VHr0w@^5W&b09LJ9hU^6y5Cs9f18&@EZbyCn zZ7ei%nCCEq=LyyuLMs7#LK2c_Rg%moTA*vhk3quskh1zi3chvlB^qo!=S^ zASg+gYvhnjmxW+i+*M!G1kqnv%oO%~FwEiXKzPHMin3a2!gO|_i}c>F={jih^WE;v!ec2GZ(T*F3u*lYSdTD+<>>rFy-+ryqYjbq>!hNI;m} zTir1PrwPa~kEEyod9gj9pobPBvmupsolFZyYlI=)FR{QcR+jg%9x>Gtvyts(QDv*E z=h4iG5-|=ED7q41%*NZl#IcF=7CDzQ&bq+n5n&Kv|0$FFdmaR*M;Rw;NG+lj_t=2)y_<^c^p)hythPCxeSDuTsi zQsJ)?e33H|A9Q;W42F;wFQ}5yJU>MFt=^}6hv0uPE|3&vk12!khbS=~$~-#6R_bMH zYoO~qT_N@uykM8Ty&Krh6}>v|@2BK-&BCIR9p{2^OICt#^!p%)s zl43`7y9bFJWm4K@^g9Xj{#Ca;_@@(P36!;eN^n1j=$?3{P$@bKwl;Sq-S9 zMY-FRuO7nAU%QDAgP&u=TLhm&Ytj)(OuDgD?cecA12u1#|5ew%oOI!3XQl}Y0}uKM z`ZmJdcc75hIgx$$;2PC)McN>u%V;P_HtVu&#tQ}MHk`fZ#@1~ck>s$e=NWfH1C|i{ z$tHr2i@-;Xq-pb9zjW+mS-BbkeHW6`5l%4dQ)JS8v@0VXmj>EKyiLC&3;Y_5vAg5Y z>UuVoCh?dOq6~X(<6JB1x;)5TnXAHo(%ONK>u#v<)pQ8Ujsed!FHvY4eF{$ZNpGC= zDBskBYju*-enko`B8r!(&C38;RL1nU6kbZ;D>Zm+oann6f%mGaaP5uXBgG`(---p# z7OuhIkQc9?s6y$YIk?p2rErEYUzea5xOT{zC;>%J%6>bK2N#CgZr&yXL}o}Vk4L{3 zD*~zkyGl#(Wv3lgEdzM=nXNDz4S2Pz8rzZ$=<$bftKW;o_K~gfzXjJrrM-N~PvB-s zY7a<7-$!4MWq_Io_U_$_za2jY(cyqK&Wy%;UC0r`5b4{shP+>pD5&EwsWeR@W3;cU z=kcjdIMe;7WWY>MJ`>}`GGG$vaVDmIbYna*;L-VZawE_B2*muLp~b>JTpvFAP0lHg zwbxh?m=*BMQx8Od?f;L}eKRoGXeOqy*!Rw?Ge9q)SqIMo_#er`s%*S$50L->002ov JPDHLkV1kn3TNwZV diff --git a/files/opencs/scene-view-status-13.svg b/files/opencs/scene-view-status-13.svg new file mode 100644 index 0000000000..1f2b3ab398 --- /dev/null +++ b/files/opencs/scene-view-status-13.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-14.png b/files/opencs/scene-view-status-14.png deleted file mode 100644 index 4bddb7ebc3fbdb999de2277e1837413dec770eec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1302 zcmV+x1?l>UP)e5nN5fsMHI)YduBt3$wd$~@jGA)A_~R?jE5-VA&OuW zRDviuxE_oiG#*^Z7vw`Bpa+Fqym-?@4|@uHi2;^Yr8b_2a`33w!1ABGac>44LZ2?*$6 zF{en`#(Hv_(D=74b>Yu>+%wU-B)bE)J}9KC5B}aJ|1jwIJmbLDvG}Bvlxqh7en5GC z&8yX~rT$XGWLJS~;>+0RLWh+M&o}Z5{ZGDJ?7~ z@!5J8BMUH7zDxVXjO;btkw6e8HbN?QN6pHLl-4b)$jc1~G`$W@f29M!YP|#vH>Csl z3g!DhsUWamoAfQQjWWt4K3gAT2gYMFQRf8S?+W#2vJspJsTof)LJ zB7G`5v8uN5fe(+h)aa2Gk;qqv?-@~dtgOe^3N$_im>b1Zh%$%K^dnV)%f83jeKKPD z=Nno*Ga(Kd@YhzGKFKIpsrxV;abQ_*b9T}BZu{>|&SJyqaER8Qw7}5#kGj*Egux5c z<8#3F&v)tl(bRhNJ6vYi0ZG@7clOJfrf8H6sUI0u24DYEs*-Y36BewDtitwUaD za+#3vfsc>3)Rr&t0pC|2ZCs>oT3$;LNTLAv2HN8sTuaT_V0=T&OYh`SDiId}6JqYY zIkfSIFAdbHE4!i&xKil|tv8G$bZn{+@cf-Tnh@MUUTpd+cqGv3t=l&Ae=nvYC+;g~ zzlPcEF;@sTk+BN!tG-u>ejw1cC}B5|Y7A$OhYK-CoEAvNOX0zv;JLKDgg@xBCmUX{dqZ4;>6EwIV2&rAK5O5{HKUj z2>}5OiE*6z0UMhCA%eAq;d&D!K8WQ5Rw^SPcpzbpZWp%5tfuLE6e8ca#<*lI0*NT( znvzSl*W8~ROGMZ68z3?P(SW?2e3fHpUr}LB?0VD1b|Ez${|KWNextSQ$Vu`SWWpL} zs$W|cG=975%yTOqlCM|E%UtTaP-@X)8nO#7clkngZMjHx;@=``=w8FT$Hei2xrXYT zpHZqkrvklP)iU4T$}}N`eUAbDw9#S~^u^i|v1S{pb#_X%e>tgii=ZXp*x^;B5{NL~ zid{5%`9q3_0H13D`h;r#I;}KX_v(ve$WiLI)E!r}Qhgu^4u3evY5lZ0AF`^9G~T%| z6Kb1w6seA~#uI0EtvxPlxuQ^I1Oxyi z>smA4oxo)3VqPwdC&x?ue?>q5U9scqCcqbSE~HFF`drCHpzJW}!jY6s0)Eumc(P;A zdZj)vD5zfJTsu%j8103p5cvXuLe!XJk%b+|p}az-F6=-dYRs{S^B^PM%H_Q0*SQ?; zD|~7%6B-KeqK(uJB>y*7xT;E8FuqE#zWm-98qHgMBu!}Ozzd1L0kFZn;oU + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-15.png b/files/opencs/scene-view-status-15.png deleted file mode 100644 index bdd43407c9e2539051fdea0b759500923bbb98f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2701 zcmV;83Uc*{P)e5SP5)Y*A+c~=FjHw7~5cj4IW5KZNeUc*~(_N7LQ3- zf}*woMNxsOB8}8dZE2ID?v;pIP@5WBx}YYg3WeAvB!Dnm!V;F$!GMDU#x{$=yFDJy z`g`7=`NJPG#zrJiOMTLx_x}CwzjyAv=idANND3SJa5MB_>?(9wQ$S+V#(kkDae~BY zh$Y`~L(8j%mlvUUk;8oSz`42LW*n%29(Tny# z9w6`ykPJr2C=ccMsRseq%Xja>D+w*c%Ys+>v3@~7vP17VTG zwUv;8b3U`QtjDK~@6t#E*rop$@4wqc_FE+=5<$4p5n_-RxqWC|jMX)*B2lh_prt3% z(#Mq$!754uE$k5wC?5=ip%$T#2G+x83EItP(d+SP>)Jii>M{ zULia~je$L7eylm+XC^XW#~c%KKQBj*6|{H-r)CSPFw5+qrJw2T;`(_r7rMq9C26JI zD7n(i#_Uv!j6|aZlcb=j(~k~@m_MYT)~m)En%tc)oj818qkavI_c0l>u|1@W-|iJO z2+!q_sAhGdUV)*cPF481VxD5fDoI57j3X4Hu)%KyqV1{B( zME0=3NbBDZ7Ly59iy59y;$RZ?`u(_ZyAiGK4je7J^wOT)yH!QvDo}XHqx}4$NQdF{ zEdr8YpC{12YoO=_L6O%fdsUNl-#;eJ${)3Hs8wD&F4c-;I`e8PS(lQEjI6*7(gDSnV1l0K(lPX^$tJkd?5tsc~O43-#1E%iVDTPF&=35 zD9Yp8c$iVbJb#P}9e&}C2gD0hU?97CDj+mf8)aYQ;NQId$m=)BJD&9pkPREN?WVO; z((UM^6;CyKF>&HpwxfK=FH&9-F3B#-s1}9mWBM8>*M=b%j`GZJhRFY=& z7I=k;;3NDt?%d>8@U3+(bIpl&(!X%a{&GKFc%L!A#rEc#F(Q*IbkhiYHXjwUa3!j> zffh=?dJMzMH(|~oJ2Pt#CmXz&oI4gHNRbeYclyF7I9FK>r{aS%*@i~)yrRVm9}D3? zvxNSL~^s247L6{GBKnOtMc}qaT!REm910_XA#Zxs4%^PI`-t%EuY6v+Q(ad`1 z?by)?-u|A~y2OqK3^}tI^G%nLBe8N$CLc@q;Cv-ces~eN{cXrgF{83I zh<(@FRO^qi3%VFpzyhp#bwy3N)&zJzNz%+8veFTiYk}e1-2UQ;iGgz_d2uC#eElGe6IfNLxFq=X*$$DYs|jX|tE{&rEr zU05{_^hSYsu7c_0>Ot3FhcQI{R>^kcO?g7K`i=S~{B8FkEFYST0L@!^vmH(gkZBE5 z(n~Ae@G7WhAPVcGGg)NWB*sXrrQ!iUv8T9E%!-ROt}u=V=DP}h_&K}zmw6d>RMfY_ zk)4C_*~7%5I7*(kUb%uvnaL<;98|S+VvNH;u97a?_$u9hWW=}OPRjh#(&Imqk57T16a@S?@V8dumwfw``Nzm9TPo}V@_0jF-ZVMOi|q)Y|^ z^uo51gBWX7$aO2;t!+h`F@#K;A!^yvZNQ0Ho8Yx2lc#Dm7g(0A*t0mNH6*V$Sgd9z zJyGLP&>e8`f@pEE#ue%m$jdL-mY1IJTy{STj*=ELXHUhz{>i9oXvXXB9L3Ts2ih5B zCD+<9y`KR#W?+pK=h_vN&e?*1$reTs`Hd9I%e;MdY{^%D$G*BgfVEvM9`h1!;$n>} zj3_|MA9iIV&d5wKp{S-6&pkaCcAFJtm#R>9`XkJBCZm%1{6ez_PbL{vZfkU?>Ih-y z?5zmN41{i4L=o}IPkC)+p{(s@sw$3$RjmW@Ueds)Ta9?9&V!Xp=D}vMsNl~L{5)q0 zKDh0M&*w*OKch++&GPN6n&lnJcL;t%ytOr1X=~icr?k4J;k<%HM}*~5%m$PNt#Hkp2$L-1(Ao0{RA0rYGzUs= zv>`=eC1sY;XA%*NXIeuo|JYfS%&Pqhg8PwnOwl&&RnK@4r{j>%jbPJtAmBsIPbR>~ z4G1-g^{%q}T45pb34WkW#@^dr6wJznEE{oHfUl{>NP^#0-GU4`g!C@p1u#!YLX}rp zEykq8I)blOttbS&Z3M1(-SFS6K%lM?il2u;amdtMtXn5Wfqnb;qyE?d47QuFhe5f* zHA_8&9XWdeegS?!BHkkS5sb;CNMX{grKZBm?qKubw~NJ}09qSIUbxi^q)EE#1#q8- zpf4eC`!W<-U6RFj56*HtMvikti@JienVOSmJ~lfe5eKieV*Y{|NKJC6=b6^ZN{k}- zT~`U7A8ac4E+8~+9%>41*;Q1mdO$ygbSmU`#cCzQ?~bmfj95I_d_8P!-bVubEZ10Z zbN!@pv6e>hxD;ZAJv3vW9c8!LFlNFS75pc)H}P@Vc@=yq4`DfRzzfa?>DsCe1^XL= z+jq4o-_!%w#-!64CFmbc6t7U`7664;y0v&PFq*(8XyCPVy7sCE_U_wZ}c zQEXV2HyJJMZoIK`AC?XufRne}bf+IfbOow6m9y(+`g`YA-!T963zw*XEEI`nas7NT zj^_bYfh9|q;ww%E_SH0F+0*l3HJk9~qEamAZ$iB%fQlwJMmoZ&@L6a1Q-PURbbb^NK0!RSr!QwI>P7ip3W+t2FQD@(Q5^SGWi9IBpUwv{0iK{ zysn!Q5(3& z{k!u3k7#|P#dtvE|GokH4$5dHrbvA6^f?9e6zX%}g&+P4(Tr+K?WFhf00000NkvXX Hu0mjf@MR^( diff --git a/files/opencs/scene-view-status-15.svg b/files/opencs/scene-view-status-15.svg new file mode 100644 index 0000000000..a8a3ce8573 --- /dev/null +++ b/files/opencs/scene-view-status-15.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-16.png b/files/opencs/scene-view-status-16.png deleted file mode 100644 index b0051eb1273c586e5ccfc4b3d371e7b8cb86127c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2580 zcmV+v3hVWWP)e5S9@$z=M_KSz1NNtJI>pA$2br2hCl*sD72+4Wp#EJ8MJ8_(c zWBcBn^U>=Drj$}rX_|bc_*ysL{mwbR^ZWhIXMpe(Mj(tp7=bVXVFbbmgc10kML@vJ zv|nX*?5oNg}`=47^3Q351AQ%kgNI^tNg2B;o zxN5b-yw7*+?RWqBEqwgg9eL~mh$_3ES+}1M7X${g^M1J0&;n};EOKI83_fC*_(Y(& zqesfkOo5!tR9+l)#FUBJf8UwiA7ts80zvojzVlsQ#Y$R$aH1%D7uUugtU z5TH0G11#w&JaM9xdWoV4<5Sac^j~#vo;vl>>-hYUsrASOsEB7IC%_%M3$z*$3N0Ca zNs=^*ECVHV4{?{2mz4%-$%$~S$p$64Sr8u+4Kl6~uqZ5AX|#1PpFaTQMY+&7GzRsp zj(1Tb-+q)rzHk9A@7;5FYh@XyiYiV@1QU`QZB9X_mB17q>u>4o(PSf?RI1QgD#|l6 z2M$+1tVlCKcmFWV`4*s}I1eJU8eS9$osx0%41(3(b?i?^j(_Ki6e8lH&%LVARlfVm zo)0n43Pf~1DpDsAiu}UlBvE78-H52F?nOM7L`<6H2l2S~qHd`(JYe}&$*#VU*Bu=_$p=Wj; z&fmNPm8{xojVT~ zTQc@<-?$b~nGjUbrXp}tjYfhtOz=jt9rChL;OX)b@Gbc76R`#@(>pr{?|*UyCcU%4 znmtvbK_3Z$z-RG!S2P-E>2N{!z$ip$1lYWO6^2tnoufR7ru28>>@^bn;lI59kMHB- zP)9-qsEFrer0}6*>*|99qn>k_5c|{){m%YjjXBi>nJI~UUERHo_>|fh9wj@XWBDp;5@dj-dUjfXJ?a5#|~k zWfi!WK^Fn;>6!cNm%a4$7vI{ssiMZ5mc(CDc}w=^nVCgV4IzDGWzH91D_5j}2{&In zq-fm?(&p^-CKRy|kzqnvL3T)-gd(jW(Kfa_!HAvc=oyreDngz)o&K#R%QY}QISsDC z5zuKQWo*jJ))wY~0gDeV9*hX+G}8Ixx$Ex;X{Ll4kJn4>CBE_v>65etw_9EIQ zri;+Rl*b!#3|dI74WJ#ic61|ZMu;^U5J@jg&&>MOy=WbMpP8FSo=?hJoDsNIpn@=| z&tl>l6ts-FEX>aNASEe*#YXFCY6n$N)tE>#=y3J>5V%*_AAk8{C04KV_YO~Jh^HmF zW_f(lBU|k*sjR>P$tWa}M68}{@q(DS968nYvX4p_xfC++7 zce5F2;E_0a68Mk`xY^?19l@ANFVd%Xc$}?4#HmXZlU#|!-ZMDL?Kj2{C1XrsXv~e& z%TS#CBaHp#C$A~yJdbA&iGy!L7nL#u;|qHH`eI7Yc26k*L=j(a~Mun@^c1VbgmZSAi z3`>=9n8|k>E~y|p6E>|Wf)woZmD@Ik?ZG*M>7xBqyevfx{OJb~%oJ34ZiU+he zOhFEM5drE5Ax+8Ao$Ro#zF96W%vNx43Ful(M*ha*`*eLOWFOJ1gJTm=gox`Sbx_yP2CMV3puWwi zbPc;hh(DO;k_D&>x9xc0XBg{^T~C!smLnX9xS#PRehBc+57E;lLPeEtb;)$~4T?@WE0;+Yb=%Nsd2cLf0rTr}Du@V(%YyYsuECpIr~}MguIiOnR5jN0i`^56(kDW|ER` zPUDDEab%-77Zk_9_>xpn<3D(Rr`f3qzb?mNr;kK82?Xaob3WY^o>ii8 z5EbWS@pBKYCvoDB7qn}4z-Z7*=9GjW79`To$_87P(mmpSC{@(B58l6Y0qRCoTT$%E zafe87m?xqiMP2dXWREc7Zv#5i4UFCsC)r{Dh#PgGfhK#8Vjmbgc=_BJZiniAk9vRX z0@Q%tczN${QOmDamgMpd5YGq4+zL)oM#m8nC)zVyvfAabshI<3KKb|Oic{V9VfSS# zKn=8S?@PbKf%d&8igR?xYkoAKpN&M7e5S7~e<+{^GnWNRlKU9LHgFat5!TJl_p)+B}oKJXNQLNyS0K zN$^JESWE(OQGzQ>DB|=8)}h3tS6F5c;*-IL2C~d zeF4;31?YGQ3yU%Mcr7HO2`h^7kdbDAPNRXK(;^&+GVF-PuUxk&0xlpTAm|O6pe{2%n-Ge9+IOLfH z3Yx%%;&2h*KstTL%1D~kEF_7!&VC1m z`i8NQPz=>39tO$@mZ&#l{djhr z#UhPHIW9db9fSSDuT=@6At7-Ox*`&;O?i*Bc62-&_ASgSPq3Bw%%MnyB_|u~Mn5Iq z*_I9j!(pjRC31F?1{bG8sB5f4c|kVfi6o9+Xobo%gLT5RfLwu@ zF_lJo&16)$B6#fAW~MWMBR!aVp76+ z(z$&baY~d8=drU^)=-%kvzSD5RQLvb;;SehaY_4@b0Gi{e_f} zN^-L3RTMQdlEJYl^b9*;N+hr;%Ro8|prT<7NhU`&t5=Vo#VSD}tqVBm@*_hfB3I8- z^f)hNWrr{^^$ za>$ypLXiwdY#$g^dlr1C(h#Et4c;F2GR-NClYGXf=eRY*IJ?FeXDc#G z<~K%8U6h+iL>R>DJwyByW3`2Aa<*Xd;NT?Br+-G-~45Mr@7N~p>xtTTXw9cp_KuJkww1c%3VZAxaQT2*n9 zp)%MClS~txgHH9OjsdW0x4p5Jtq4yzl-pz}vlY13>%fgZM{-AfCFQ$TR>+WR1}d#N z4byWA8#8TYwO-Ki&WV}itk2I^szuneJlYZh_T9Z1?4ffvdJr5M!unhb&P)biloBW~ z+zei@SkNEkVJATMZCQs6wJR_-;o&c~_aV)iBBmG()PF*Bx~IjC!BKTZNdcPHR3SUf ziVHV-Ia+-wE6plV&T!NN;abliTFJ@t(k&86i)Vq(Q*(H}eL(7TxWBzrg&y1Y(9bMJ z{qy6rJUesD$kwZId@78m9^6SR^7!yZFXH24s7g2CtuY@e^njU4p;Gap5{D_3UaDAG zBCVvV!mcaYmD#3u3{xM});}UkW7ozy7^$kTu2mUKGErL5i*3Esz=={g9N{X9^W;vI z-FLRN4^)(;;i>r-J~;i}FW7S`<>Bt0-uFboXxe4Z*i>xR;$2q|Pwd$SftdeH@XVO# zVP-(1qE^hWSRqxdDCVhZam*u9t!DYv3_K0SI9#OhuhrL5Rng%}M?Y+4Q^I64P_UAm zoUAEqBXZMu~X?F;C#NB$bh{gkq9_lAH{PoOoUz+6SB{ zq2*TboaCfBzP7S#sT{JPSnEACyCBP>v8G%aaZPiRv-7AT##x~_bG-+v%k4D3LmHX( zE-|hESI$c-(6nXq@SXy*s>>5ZSzRsl?HL#*8*jhbhV?n5dASLbVEKsovz`c6Q^gsUJ$H)heEV)8-_}F@0yIE}4@&Hw(fjXhqr=aIgODBGy+GaRszj&}h_{ zC&oL5-BSCo`{1!-Z{5VW0$e%E-Dv%WjX!V7G-?;AANQM6v5Q#s1w%M><}w~COGnF0 z2qrEGD{VNGYExU12a8d_>C0_=R=N$YeAp3ld%Rj!)|&5XB{WG!3eh6x3Ijk#WpI zGwnv2fs)xBK!b%$ih#2wZH#pPzRP=L+_P|;+_fo#l2u2$8mcG}n2>HW?6$Con-9 zbe5cqarcauazul+fiY=dV)lje@1K&(p#tbm&r1O)53)cSH*Pvyo0s{tFP21sj>DD7 zPj4iY`<3(8AKmv8@{8|mtSLhZwE`@usl}GaZ8)*h_k-X^QTUp_@(mf z7dd|_0Oe&Ykag?UeYbaT=(nLy?0}U2Z+Cud-@`u@wCZQpSCQbiP64z2DSYm7s6Zrd~Vc4L<+j7nI7K&cA&BG6TyDEHkjoz%m2>7YzIZmGX!F T1D;N|00000NkvXXu0mjf3R>E# diff --git a/files/opencs/scene-view-status-18.png b/files/opencs/scene-view-status-18.png deleted file mode 100644 index 7d92726876877243fc3ab99bc163adde658d58bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2947 zcmV-}3w-p6P)e5S7~fq)fGPP&EknY-uHDfUg9kwhByI-HPEmGaA=5v z)b2f8Sd)fC!66~5<6y^YJl^+xyo@~_&&+%M z&UNlEHl!gq%8$xVnz?W8yYHTJzVn^$+#y=JmJnD%U{MI9|7G$ed7xAuq z^{2SmBY#}(aZv#P`R7Sd4T`~N!+q)HcAo7i-^M7M%g4IbuCj2CT#vb2INqrHeRER^ zu+TLXYjnBha*W?L1OI|Ek1cTMIo z!+a{KYBy<`bk&V#q4LM&8ox~jJ`T`?=-<*YINJl~Uq%fO0_5pC<+IB<1_A)0kK;3s z0{HzrP>vT4={g)WZNl7o=^%o52w5Qncoz5Naz1F}{mntYFZr#2?%b}w!`O+BJ;=&g zg;dfhKofp66Z9On`os7-HGht%wDj(scVAz;JuW(eKK}gVYkzy|pATToyp0yV^Id+6 z8W2b;>J9~T&^6z~K=V&>Gdw$s`xQ(TJbVw&w#sMUG;S^}-Bo&HahW|e5y?SzICF|Z z_-Rx&;9Qq%_#zJ)1Z0 zOpc4%e)HN@6c5yW!3u?i2BVSshDPXUbra2aJg@)tjeq8`43nVT!zsxj)D`IWko?-?(-=Bd)3nP0x7gaAhrBnP=DHq9ZY-!0}OEO>?I) zJ3WcA(vwASoRLosRVUR zSp@N<0%Jgb8ytOC8d*3>#M2UE=}c1xnN0@Dx2JiHMx#kbPhh9!5brSNWTa3^VjLZ- zYM_!o!fQFGi77WZI(le%N&>a_ z43OJ1OT~pb6k;}spt#W48AoRyRX4T0{^wWUd=Nh`n2|sqI46hq{pkfZ@4ZZsIw11*oY!d%BQbw%gj>PsggyzW(Z4f?DTJj{Icq`dR>Y-gVc` zLVMcwJFdHmVIf9RUW~rUWTbjHc%r6>ax#+WhBYh6GwThY&1+4NOxLuV-ud(hjk%`% zWm`5HtYOQ@=ljY&?**ihkq&C_9ikAEN*mT)0du-RW{&sbtnlak1IM`FcmDmIcOSxZ zz^On067j6GB++yl+PWz=B1~|Z5j!V`-qxN0lPx)cX(HB@&t=3L9}{I_!?HLT@wF>+ zL|`Xs926dEp;b9H4aF{`b;vnRK%5FQ)BSn&G<9@hO6lw$qBLX#@+$$+I(i3{j=mwK z7<*YQA>^E#nkT=?z2CoQ*Im~am)TO{#Y-}JmcNOqX%tmI=%eXwk55^#JcSald2o~B zbyJ|tf#X#u;s_w4s6~01f#PHoc?}os>Bd%yKqi_y`!rBR&9SBOW9h6Y_K~qk>gXFJ zi^-@DkGqts^0UZ_;Qhgk5r9sD&L4bs{1r7NA+~J7<>K@*U&V&}&G*x2nPZbKpq-#t zLd-Ni;R+}QFXYtvAcu9$?LaMpq9Uw7(nXU~(_Xn3ujBhE_Y8PGrkT+ruvX=O;M7+( zv4#~5zRXU$J(Lt5t3-u|ao6@sP^C@KjG8-oJOJ(i<%MT{p+|*Tyj=sMCg$nNY@0SR zHlbBFbr_5C?39QCx=T>AgSdi zYls^j7pHInlN`XwT8GF8Z0cR0PuIYRawQPwEU`_NUoQ5}z9AvMP(ckg)&5~8sF$X< z_6#b@Z=ZNXw;32v2JeC*2DN-Oj&|d*Gfn8!iFEV2RYJ9bZ8H5Bm{Sd{ z^krkK|CZu>Lx|Zdd~5C=B!f93mIg+~N}%7yWtI@Ny?4-GXirt^Tf4{!IxscNa0pD% zE9)IfJEC4+luy^JT0zS|tNq8TVL%gwhn_&l9-lhN5@MoF*R2ujI(vuIqt%TR8x^jF zhlMH#RYNn=&Nw=Zd70^S{gnljgiIg#vO&T7ImZ$z84i^gC)nm#sS6CaAHoq=>?xD)R&ZwwaPgar9t4b-QPj ze0wUuFpG5uPdDP<3F$5UqmLdsuF^E2FGtGUIkSN zp%Z7$QgnpXXAKKQuyb9Q4~12YEi3~1;OMw=?dn_-bOaMl)VAooBNIP=@BQ~Tb+ok{ z7n}3`@@C<<@%B4@WlM;DdR<`_HMI58TJU|=>&028%LL`3jN}CUP-UHx3u%?b$&QG^ zLr~RC?ZjsygR1B_Vn7bF19Heg@zJ{<#}B5*x|KP4Tlav{H#|xOKs;=jg~}`IX?0Ep zRn)iYZ3E5#;tTyeZvb-P?VIlWC2T!)_q9bv)?>b)5c&s)zf?A?DWHPPbPZ0cl2+^- zHuZH43>%>m6bhOM)j}{_=8{${!8l84pp^R27ghR?ZoOJaHNVfbx4)uZcg@T`^4Y$< zFAAgQ`3g|zJkP1w;rQI)aQwz=vMOT}uAAaP4HUa#fT85LNUErHC@yF`4q+UKj?T&G z8?w?Qb9p>w%XA_9*;*9I|p&9P86q-krYFIpHJZvfGo}tEwwC4- z=krU>UoZo5p&Xf7_QaT{6QCM-wq()RxY&D!oMx7_(wo*%OoWw!C6nLf^AS7v>PH7D zFFjt*wWSEeIXIS4^ab6~J91vCNc#)Niw;0;aLcW?Kbe^v`|KJtyRcnMpT%UA$2^y?xMmL8?f*3&-yqfZS-~ zZ7BA{=%=~h;3v=z<*azn*+U|%q2d_bJ2Y3EEQdXVPSk~os+v0WrrzO4zxZswkVCoO z#g2;(KpOnPwyn>hmLD%!nJqHFJntKJ>gc2jA4j-2A;*|Sd zXuM9{kXe5S7~fq)fqkS&1%nhX1wp?*p3s&3j|_2iAfp~2#6&P zOIXyRsy`5k3W!2W)v7;QRjpK^(uKB4Xz2!}P$&=}4O<}r0trsy*pByo-{bKt_RQEb z@Acdl-ozL}L8&5D@{?xnd-vY=?)RO0zVm$-SiY7KSVrK25XgPp{}_KI7xa+}cv-%H zclB4l$(w`v!^Lhd$N?h%C?we+8H{0vM7yP!+AY}yA#6teDm66I9QAXTNIrt8^Pts6)u?&Ge24z~0Dklw$j72h{6dj6l^YS4ZB)t|8) z;4gpCD(wY2MaC-xzu z*=)8uU1_ew#IpD}t6QR;ghIiAz|2hZ`1ttX((j$`S&IP;X-R%aA{|sEazD}ht^NYX z+Ihc9P=$jpaBP=8_F3)DnwotzTPk+vXQy*>xI1F!Bnkba6L|KGBh3^(bGALMD9K`k z-5|jvsqlmrFvk#EP*e^{3A3h;ZO#qQWF}R=!0*(%5g% z(7);ia*m+L-`V#Iy-#liRd#Nv+#?W|WeIac^T}7=hB>O>(j+TfMxyT{>k^ZYnVSuF zMhYCZI0WWG=pC6rTi-B-MkkSCl8|bmAaJ+LMj0b>3mBhQ5arHC6;&M?8r;)RU;lf3 zUA+|!2273mvA}BO=D*lFhqm>3sEF7Ds7~eg^aZj z5OK{DTR&I!ET}luKlI?~y1HL!1N_rt;3stA2NFL<^lzu3&+8MGwmRZD>B(s9=!e;4 zKxAQFscr0%w`JI-Up ze{nRH;A<2TYo6u9xz&ZCV%!^%@Wj!^O@lq%ABa)S0dbT2c!2MyLJo_?^U~|~UAOGL zcH>%&Dtd6v_yRb1)TM79EY@cGrCBn4qsS9 zKa&va1}`=C`U2WaiM!IS z4!zZ>B7##~Vxo_Mceu6@fnZQwX;36LQU7p!7Hg~4q9`{53y~-ed{76&)5;{06BJ3FywXbgvsH@*1$!8h&=`+Z|) z-#+*MDF<%2@y5NS`8j*8+jP0MO7vWcC-KVpWlqV8t6lKBO8HWW~u;pR*X&`4!xD<5rlAM%1J6?!XG8su_QTw5YnW8b$6*El_{+kJqq`!fmCLKH2R-5go}SWpv}40t)V|xr)JW z!)!1lGP7bf8i=q%4uKtA98= z8=Ajc`qhuWr@Cy`g~9P@lbso|Y-OIpgrhWc^cyEitG3VhVkM`=s#wE#H){;(8k&H?>`2D=nVF4gi4J3&)gt$eOhl)=K6#B%L84hkJ@tLp zHCI5q^zNw^1iHGgKFf(iqkh=b2y){;X}s0xM0;3q6UK@X-$5%R;h}4hwIvSI)U0fHvR4Q-hT6!;yvvoE^eAzTHjQx zHv4vWYIVNDjDte~+_hsXtVI4ZjTecD?}-G|ub7pb;#F$Ns(hKMC5ar7Iq38 zjv;dVD;4Eb6$?IUY)7KQ98z>+rzi^Rnce-fbLzyjSGwZT)ev;{uw?nDsaqX6 zF;dnjPXOi0YHHuD_oai1dz+li_-FpIYi9m6dvQ&2lG3%eABiw6!}ZORL>;j~%t_OW_7JtYyP zg>FS`H>n~rr?H~62=2@@tsD`Vy|%ea%j_$eyo5v^Z|dLxD^%w+3f$>(1@uy$MgeatPNXn=loOpxrZzk_3Zhyk6FN64Oyq zm@ATx_{|Sbddx!BYObsz_3SL{&Pk?NOk`rA9)Eo0Fp6_iRUUFAaf}p+NysSkg4#K9 z=DbwV4PM%66iP}-$?(EdX-G&wMd?Z=jwzVNhmp)0{@}15l};upx+;pa0rLKxAu&P*FmO&IIEC%Wh#P&&3MkflLt@R)xsuEjD$ep23mSx{; z5FbzuJ4dD-eDS6IpG%dchU?F})VGhk z^Oh!ux*KUSiP@2JMA9td=$AB2_KDq`XHgt*W0h&zUvX@i-$LstwaJ# z0TI+Ju~lYLiHS**Qy*jD{e})@$~*heTW`GfYyI5|X+PzF{xRXmx^?Tm+uGUnhuK9Z zAbtFN?H#*r{khd_ymx(Rp_RPWN&`}i=i=V~3F1rFJ+A{xCw|#y;Rq$@eje)0W1hf6 w?_VHQzU=zT^Oq4=Mqn9%WdxQH_`g8lKer1kW{sycPXGV_07*qoM6N<$g2tA(c>n+a diff --git a/files/opencs/scene-view-status-2.png b/files/opencs/scene-view-status-2.png deleted file mode 100644 index edb350b8b5842e835fd05cb269890e896f6ada8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 569 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DX&fr0V0 zr;B4q2J73Yw%*K+0&V5yw^;tL3LQDN-aa8QHCcJdrp+5frkDiQHz_zt*>_FZwrFZX zwz}9xe<{b_)(PhvM2ffXD?XWXZu5r+3B^Y_p1(PFCiQ-8S(%^aPhln}1>we^zmHkY z2Q8C7`C-$^4|@A|H6PUVuet3b&G2*9rDY4ag^eYq*LE-eS~AO3F;B44fBo&?_&NOd zZCcE8FUapOiMVk=;doIW|LkupdWvcGiZ5=kH5NZq$k_8rW**!Bc?=%<&0m^%6|db= z;`r_%{pa$j8QZv=r`>X1b3!=Y>@4S<9Y%q#x>_CINFSO1m`@{T!kyPLpEK`rHoRaI z%X7FP%iXY^?NsQpHQbX=vEA1`u~YtON#N>oNCI*WBW`TX0bMhPcgULq^@TJ)2HV zt!6uorW2?cu#X*iAq&D*QJv}JJv7{`b z)k3KM*eZYFL)TW66f~aPJF}y=)@%9?f9oU5wna#G*e`x3zp>f2=Wcn!|x{ B>*N3c diff --git a/files/opencs/scene-view-status-2.svg b/files/opencs/scene-view-status-2.svg new file mode 100644 index 0000000000..656c5d9d39 --- /dev/null +++ b/files/opencs/scene-view-status-2.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-20.png b/files/opencs/scene-view-status-20.png deleted file mode 100644 index bb950d94e3cd482bda6b4dafdabd85c5f072b0fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2491 zcmV;s2}JgZP)e5S7~rl*A@QWB3ZVDEP0W43tQf>SqzxPO%t{ufdYmC z6K2ZKCWVkLlr%GKGijJk^QV(clFqbbrVt28X{OAyglq(c)eaC=8w@tWyDWK;ZQ1fF zOM0*8KI_Q}OMu|abUJ!7)|I7u?>XOh&iT#}5P2dDL>P!L5Mdy~K!kw^1OKxO@Z_2H z`}B_czAi5>U$bk?lcie|O$K~&`oiA#KKSQzWJ{WCdy@`eZF%{-<%>$UWTz)XmSyFS z<2dvW4&(EROM7?kKd^;B_$CX;qybpBcI~dzMB~<_^JXB4Soa44As?9$K@`#M>cQD+ z8%Dj}eSd%dA3vp!A3GwC4S=P}+C}rWGRAqH!|!f`Fd>0qp(wCKlw!V7TYHqgpV*{s z4zBl&KzMPoy=$s{8fLh1WTCva# zJVV1&R2`H)l04$|bNN%#V5ZB%n-tqOG&+Xk=c{0jHxM%%@5tGPkEPU5T};r<6idS5 z=0oxmaW*c`lCFFevdLt%ZvZXrUC?Pnxx0Umn={P{eTnSBF;#I~8!MQPFR84M`~v&=&4Cpo-P-%P9-U^M87$w55j z`?pUmYQ;nKC0OXEtsA)mhHY^9%yP&k*`tD%$d|F<5ie4b5;)_OSk|=zDnQjHr5Wm5 z99|;sIqvO0{#G`|>U{RjUX4CBn)KQt_4Ik9YFmp~QkacoDlmqgrxak_v#!~J#5g^& zDb+6DtXJ}1l|yETr5Y>t{q3`GA!**-QBc9q%Ibp)`0=8)PS(4>MpwIyn@x-x$PZ?d zsS+#p)^?X7zj68)iEQ#6-EK;~47t(K#c{9y?$@#c@QuRiR&n>y?-CDxN3;vsUFYO%Um%J-4{4#6V0z*DU< zn_gLqGaZ2D(wl*0rTIdXR;&0{-_`{|Ye)d;bD7y>5TkWbe6zDFP@J94-)ywQP3gc= zLrV@(rpT9X-sYMK^$$v>Vd0E?L{nNFJ6}zZB&F~~C5Jt1>Vz&zgA!9RE=Wm)U;^B| z;n4TN^lFlIsF9|iGLVK!iF(R#9L{u(!0weW*LQ=@NlQd8kyBZ1ldm^AeiKrmO&iz0 z85bM9gN3bZMy_Iq*dMEDXw#D1$@IdN%`8x4G(0M7?s4}I;?T(p&}l`vq+qICK&rw5 z!cyw3p$)hu#XJOvFlZDJ**k~3=s#!jB`36@&(#kp~dziyY;Ss26TQhQK z#1?o?ZYCWc$$uUPkf-E1ZB)&SulI3$medDh>lbjgRPRt3|K`DaMBvU1D}J=<4>ab# zy5@-zk?ApeNFn4y6<4_>bBj=vlPQtYyqZ=_7Ks(Vy|Y^+l|T$NA4RnY%%Eh%EuR%1 zjpf0`vuRekJ5?0M`@r2gjIo}&?I(^NdFM`_!vDW-1j0A5_mo;Yv&^Q~N~h&u3dvku zvlG?~GwPZgpt*=<{w#^94^5imos%X_1u(I#$}j8exzxOvM1FO91iSgA@ToZ-Ochh> zk#A-EPQM-+fO?RnR(5jyPBZm~LQASr3#`~3F1MD+TKVGnh&Slr^M{QRbePo*Q;$7| zPoXd~NiMLYac_*Qf``{4W3&hPUB_T=s=9BgsP^w%9~ppp!Lp}Uypofe@Ta-KV)y$4 zqn;73uAgQV7NKB7SmPkZCD4x-l54l$ppO+TDG31rB>c<00DAi|bl9&wFjZ8$cdp+V zfO^sLXQt;-$_WWzfecW3J3^ry4+M38dTX@Wt+46 zrOFe>6ggDyd(`!@0jLIl^6bVxQ!T$-Ha%B~08^y)ZnsRs9>>NJR@@#MMJlRoQg{E* zj$>bb@vX(F-us|+(g3K2Hf`MS2K9^=7Zh7{^s|u-h&=n*c)7M|QW#h5sOzKyP%mac zs5AV6CJ=3hdvHg^W2TCFZ)A-y5Mdy~K!kw^1OHbH{1*iRX$TX{yo3M%002ovPDHLk FV1iZi(t`j1 diff --git a/files/opencs/scene-view-status-21.png b/files/opencs/scene-view-status-21.png deleted file mode 100644 index 76ad0022c7f67085facec469df788cb98e833bbd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3009 zcmV;y3qJITP)e5S8GsI=NW#^+53gPz;cI0KoCI?BcPaA?@3J}Ceg;3 zPA0$7=p<=PP21_ecKWl^G-)!ewoTJEH!2c`*JRwtlo#?o) zYx0JMO}jU(tl42P8}NQ(^B-S*eeV;rmXKUeu2d@ZMpK5#Y+j_*sI440gu@|+-|cQ6 z8yg!)_Pf*dD>qe>hvg`my7f?BUB0n)G8%H;RwTyNGygC=M=nwApUZ&ac67GgbF+6Z_sUU`|DLelDz878rCI_=92ChR1Qa ze+YvkPFNHi(iJ?|ZYkwFhJ!JTMFk`{9tlAd2L}gsw6?VTZuXA+#|G3q_3A!ZRm0|O zT01Hg7r==zoN4XEK;HnCXXp?n7C)Pcpr)n@3kq@|m&q{c_M*!%g7)?+n5X8DYfzKX z1U3-EAOZHp5=fN;I&v))g};gjVJ%9#;v>&;#Y9}N4}SmR`SUL(4f(|s{_To~H16DB#t4V_?^7AfF z1W|IcOeKdj`e_I(kuSLr14o#zYYZs+?um1F<#gLU1AV<8vkqkM=WCB{ePs82_0>D9 zxe=TX+wkjH8PVcI@wwmPevKXZRs-6mLa;6>$Kz{rXi^SmJ9^OEav8O08muuZ(c?|v z$godhzf#BCMJ54eV5Q&nsE6ovZW0{>A0rKO)1OkINSS`0yt&o2fmCH}O~rPmB6n9W z45yh4>I5;@)@4KQdI2poFAc_h- z4=u$63)Jhe{(E(m(J0y@9B0Y2;7V8To8@Xj8yC5U2g4#h8+GkH-`4g_$m5<$j$k`; z(>60WSqIk9OM!QyxefkMNSrSdIICWPPsReMuB%2#eimZ!1P*+B9x~@RmS!2y?h3)? zi=dp?)?EY6m}G6X5lVv*eIWtG#a5IR=1alM`t=Qt;>6iYe}DIbLr+J1-cj~$X1l?F z2OfNIcST{|j*a)+C9RSWrgIM_TyE?=d>R|G^$=(m`sF&TS-wPKePqIgKmD^2-zqR6 zPV^oh^~0(Ha?~stlQhyaBM}i36o_om3{stZ4c%#UaLToik&LdXtD@yZ8OtBZ#)6#v z-aC34lOEshH{O2t8TvL2YTAG#=y8F<6=?d0l>(Djf zM@s1eRF}>pCPg%npL=aK)a97aPI1uh3!^fHhn(nUQ0%n*9~AGzwp?CZZRzFYTmly^ zcB0chit2^MiL$~xe##r*9HUO;Wu(GlHcIm{p!VT$&OYMgY8I71PwwgR1#sZx+3C#A zZF%^iJrAy|*T#;)&2X#yN%skNx{_^onBLtTA!4) z)e5;d;qh~KEi8s!s|Jg^WMjF)NAA4u*qK-OoQ$;XUcVplcwAr~SY!X}J{XQf&bD`B zo?Mi`H<4S^tzLl=Yqk`qFTZmTYe~4(3K?GOb)kd|$tIUcg1_h&(75imSHZK12|R0! z#ap?z3{Sxs%Mhp6r1b=9ge4jbMUa)A#+gzmpF|>wq=9TKb0=~$^w@_YfpGK*?!}+| zSTyO>v4OEEg`P6xqWJ{^B^;r(+b(yyJt$WYqgn;_4|ws|=5^3%)M&fXkG69cvCe8j z59xWQJA|bMnPfO4p5>qZpn$)w`U5zHnyq`&#>wR15^+4Oye#TnpJIHZn_EJRvrNqx zm(h+)Q1Ww7rka+gAZslKouVMy!Wf7j`s9L~8XpLS zB=8w(89p2f;qiypL!(kj;4cyQ^;R>^Is=GCW2iOCB-WFm=cnDyDk-`xcxI7E;Mtg$ zW91xYsD!AGW3AvuW*~q9nG-x|KCk7WC{kiC>5t;0=f!n3rO2_En1^sw&_bp(q+x8* zU7uk#$Teye-#nIAC6AW48e8wZ8!GDDc%cLSo*pdAG2-}$4>~c9d|Dvs zG7Oa3Xhc_phm~rhYI7!(j$eY#Df@7LVRCaQ5sNI_E~2TejA8iPihBmxIW#+Js~ z`Jx;Prl>eq-Rd9#D=aHArr%5@95NXCyyR}fsK$QhqZTf9v0L({RV6Lib8`D9}N2O*rqj5 z6Z1C(&k_?2W(mkHD22STQn9?WkSEpRSdK_F8KkLe6gGSsD@A}~Rm&ZWV8Ek6Bqr?; zs7ztI!pRj_3=bAM_Q5%3+xfTm-n-v!>phdiJ-q|@g+7U5Qjf zid4^d$!#i#`F(a`p2##1cqO@tEhM6_C|}B)Y(l0MQsB8fXzlJt5xJ3!=fr-}@lfe^ z@D@r4Q=D~7u7CAv7gIc#90xIMZ{3w-{Q7}c6;pag894G-ky+VJNh-H;5y}Zz zFQxG_P0ge>sW?tWSQaUg@f!1|ydqXFEoc1%0?m^ENb@jRBv#@BV-s>x2`NikjC!#1 zf?qJ9D^`rRqa|?iteO>WbQJ<#MZmeP{fJIpe)ibm4}Ucq^vq|e8m(Dg|I3vbI%SaR zajzj28wk9|AHb30XYj~;3!29R&{Os_(Saiwu$4u*FzVDedZvZXw3ty*Y!yho&_6WB zGN-(zq6F6L42f-)*_+#Yq|9DZ;KdTnqsv3_BxUpq(J(E+s^U)c6b6zSOJ~{%1qbN_gDwRy*Sy>yW!i^nL%8T+?|LECE z-O8T9aWt&1lgf`6W$tA5WSO+SYJsF4&%bd1W%=nM9dbBrC=4$JXI^*`r9($BU~AcR z=vr{j270S!DTq@omMjkxwX&2H)Ktu;#4!QI*bvgR6wE_DN#|LTW<{EV{QlUWYwv*5 zeIO7DuS}<4RVfuRomv$(8&l*CD!ZA?(Yctp7L#i!bFz%y(dUR4=A`o_)+~W!nI8+M zV}WBoVwd+lyZ_xkZ=4yNw*ox-{d5qsmF}vo+f%n_!IPBVu}UYP!xe^Fh(bxU->M$p zaNowC6l9uzy12wjXID_*YuS!OD6^{!dM%={IELwfUS60h9o&b;T@)7zv~)Yf?%|2& znvNZjLu@9l7x!k?fzme*4j_Kc`s!Vf~`{ zNTE`IDH4^~A{B*PW}1{a;iXtO)!HpgcmlhQ96EU0;7rbYr|S&{BnL1bh;~mS3oq@~4+o6stp#C@T&pqR-3_lA!O> zpPbXFRA>e5S7~rmRTe()^^#7~WKU;LlY}fJ?1W7R0WB0nf&-LR zjen+UR1kH>L9H6ca-^oTX3COrsZpF7R8WVpWER{J3Xw%Y_CO#DAzP=jC!KWGPItdM z-+9SxngD`^;>T2ORrh`GZs(luob#Rgh{oC&0%Hh_3W2o0b-#{Z{-_Qa#b$LBd*#-3 zc=$rT-0Xf-1_1euq^JhPV6@}8=w>U=@|5cl3gsqZU6o_aIFDS9v0Nx#Cj7pkAq7|n zO~o1^*W8T$dOh$FlzD1|LN5uwKQIJG@bjX;z)gC%rU!uZ93XvIXi!#&Fd*E|x?wzp zRJEQoO{&_DxiI;1v-<1tz^8#QA^Mk849Ys8{2Rytf`L4|OwQfRK41V4eHy<@LICd@ zfpWaAkPuMRbT`IskqW|y$KVx$f#rDKE60O+KHnVle#0Mi=!Q@IGx|FGGJ>R(E2NU3 zK$!5p_nr6u`5@L@a?r>Y zU9TVdt7t*tnv9eLI0xQg&nXIZ_4Lsf z2adhF`Mqsxu+Mv?y+374fr2CGP$9>|)x!R^!jP%wcA=_x66VqiMG;my+%G~l^mLl$%#rG@6hl${nAK%9|w_x98IS|^Q9 zwo#3bH1*TiYuzOy!rNf4_}WVdi`gGBbPtZj_ytOXMd{a zciD=-Dvvy{@>3vJ2t)_0p=M(uBCg1Mn$pNZM}-YbA$m9jYr|4$pLsUf$w&!mgMJJv zXEvzb=JrlQeRDhc0abohs%8xfRoEl4v7yKrUwuo*jFn68crH3V(RQ}9bQk6?E|QCf z`+x^VjO#oulauFop}TK@zBzK5=1tA@CB#J=8=BjFr!UkS2Qwd_?V-~VeiuWS??%(9 zn;x)yN7dyAkrO^0aSIR!uTop5n;Zz6G+PYC$3{_WN0<2KSc#IKnM!#XX*%_?+^YH} zI#gV~Fru^2|`X%z$P0+_R}5BX#Z4pUnWOg8V@UF_I>ek~otMuh9M$A}Hnp|{G} zY_ccWC@nEgU$=M2Bi{I!C=&}l*gscn_T?xjhyi7JKNrb0hB7fH?4)8;{M72|A|Or$ znQ8vKj8wJ5-K*3!wNffP0{R`wFDP2u(4y2fwkm~_vdL-*Ay;?rCG@MT`uV+Y-7~*% zjXgPD-z2SP`g8a8A*q@WKAPzF_>`RS$z;Rf!9|MK^$rZup2Mfe783zv6xErzjy~3s z0(lXzWT=|c!kQi8*2oGa+n%CN1!OXO%9@jpNJp=5S;_W*61VhJ%*m%Asx7`%>M>jNF0t*ilR z5fl|+1(H24(1sIzU)E}6Tf{nQ_1Z*0CgdTZHRv+f*YBak_*f-sTsT*4 zzci4~VVMXss;qT-0NjJhtAG5RhzhrO9nBpkYj`O5+OD;Cy0zkpTI0053`#(PvGpp@ ziV8q z8Y`6PKs*9^uuNtpPWHOSR-Jw$tzjBus!eS!gkGwsa<(YSi_iR4*b`z9B)D^Gny;kF zX+)#NbEoQF2&OrwVv3*-H=@qQO7?5{PhN!QT;4zm{L&FyL@ z=(m^bhF08zhl-v@m}!3#k?Gh*{~d)nh7hw^*R8U?g$(A1SOP!KvqeW3LoFd{O+$;n zAR|RRSLGlV!hu7>422LW;`q7qN)4>OW!glVJ2{6!5mvho7sDh?PduX@!*0HHk|o4M z)1njTh{m527md5JIb1`Bh^1Oss6-n_2mQ%(P(w}Wi=_R{{p9dyG}C)l%}kD?4uEs8 zxI&z%ay}Jcq5D^_+!z@ix}M!OZ}J3P4$vPgxlnHg-3k0D^QPqLG#cy`zW2DgdT7Vq zBV;if#kAbs|hWDo=5 zLQ}_+`}gd6g?9uDklXKEvh?@k6JlPRf%p#(4bhY4*trX=oIfr)$}lP0P9}JSjbOx? z4)+}^(fF|vBG-d!KmVj8p^~K+n_3<20Y%M^AK&LY(37gP>LDqucG)=t&^hazNCiQ;`I{o zOE+G@10xqKxqI0kaLs>i`K{B8%*Wio5MsxHlggr*`IMiTra@^{h85EyQ1LmM+l*id z3PbVHSqsMWXi-$|&7uznv#C|(+pKnZsOX4yl2ZN&4Yrp(@%65quZ`3x`2DIL2wud` z2(1n2w&>>zCuY((&|Fzf17)S!sI1yaxEJBhpRO_c;HEiLIdRj}4F=khez~$wK=ERV z{Pui5ZB}n5uZ;2GP>HVF`Gbg$)a$wpkQ+I)G7@4o*iawx>`8hqaI!mFU1p}WqJ^_5 zCc;WyU(i(on>lZoJ>LH08_G+I7rFLi<>mfm)Z{pQ%}|kYNA^G20C~{jyO8V&(Ho{_r=wO7&<|s;cu?6xBCKKhHM*g-gYq*{ zP#etTY;hqkOmwQEPE<6sJ$dl!-8vo0^L}hUY6GOeU);C)CFJtc^CnHuJ;03A*ya+r z>?wR5;pBGXDsrH>LTl^lUBCOQFMlvO<+<1DZnU<`pV1jY~;L*V}kf&T$B WSqVTPHE?DC0000e5SZQor)fN8Uo82>>8QbIC9>;d&pOOpnuD)mRJsy`~!0EN;dEv02mAW%X`vy$DA#98gcYrM^j_kA|c{$9_0 z&Kr#55KvI0N`BJ#zIWH}eD|F1+-qR+nPgy+fjeR#bBFsi`W4>MBzG{Fzk{*z(5LBT zzx;8x%R4fFnBNMHS8xht7QL6>ZSZV8?z=2X;HFc*7RQFzfZU(zrV;RZF%llF;)n}T z;Hi%&_qiM8cin-H2+RxP0KFteBGD?^2==}-T5*?NZo31-^gLqvtSAszQH&vmeGD7b zr-SF)AqX;5AGO8EA9t(#E<5mf)J=eoWncuhgTTK*F#s} z^u3i>PLu$NA^}xBNOcQkfMmq8v@0M38|eMGTtB8{^Q*^v|KP6@bvI`H6P0%QWsg~< zfddZ_Q8!^t{xR+JDn3VlE_|~NC@){VZS|s(jg~kgK0jIcueaXa`6Ts;PNY<;)drI} z$s89qBSxpSa-=7Izt8R&9>2Zvo?^fSZ3`k6K{1%SfK@`Hz5u1ON2T)rBz2`Gm zp~g?BZJpdUQMsnPe0%xgk`3AE$+S7NJ8b7T4z{jd>^f9YL&B#{H|X->HA)y198{c$ zA%6%X6k;JkM0-GrH8i%iZEC5neQlyO#@2BJaGV@+a6Dy*1~;{5k?XXL#(}6M1ENwo zcA`httl5@oF>hQ}IunV+dYBZdZ*Ul=F4bbh>Bda678+i}@JJ9&UJX%iK>n0;Bqo@k zQK_KSsNwSmm`5TI3ZbiS5H9ZsK00u6Q`MErw={$q@ENf}NoZ8QSwIrlpA*oxWq{O& zxyT1p_p)3km&{exEH2&180UEoBgE!ApC5%fB4BpB7G@=}?;@{@i$`i^I;<%c81*`M zM*L{)>_vTB2im*(U{P^MRFNRiZmE?#I!8k22?~hN&PN1MY;SjLth#dL@AA5G$qdLK z_i&JV$aOc#A@xb7#(pv&D*Po+zqLm(t9;G+7#n7)E`t+8I9pW*M~egVll1Ttix&n1 zC@GnRX<2DdDijzPcA>%Eg_@e{n4;y7Zq$;|1lAiuI{|iwB8aD`)RSvz$YV7`2z!FW zt(#UJ3W>OEYyZ{NOP5}i8t9K41AinVe#_Bh#Qx(1bhq4Le7SV0;Ns=(aFzhPO5o|H9$%7FmpmCix|xd@Smi*?Po)?|lO48m&GVUYa1!5Ki1+$>qm zA%T860!!peF2vRx^@p!lXFyrIGtA-5Gu8JwTAIIPt&#!kC3A;|#)wP~3)uJg=kaxq zZF^wBoQ>A>bX@Y!#T%glh|X^0Ufh8Pbv9%gRHz>G!8)UmfTz)L4i{>hPur3pGD z#i-FluGHuXNbIK)#WLXWU~jD5L1ns(gKb8LoTM`#VsBN$s^*6JgRE_gjNVEP%eOBp zDcZnP#AJx^E4hfaSbYwgTX&(kz6lG+U4}*kTnKBiVr3a(v>NnK@VVOYbgH99~cq}w4B$gRy7O>+Vjmae10vu;awxnNgXnwy?E5w9F?opdh#D(smotLVs zpY=J12jy|m9gr*Ja%^z2I@Zxefp?;^8XlidoT?BwE3y7nj~8>w<{&RK1)*>R`@Xyc zMPDz9Q;et?@}booK%t(8o`RVHW~{S^y^qDOHk#1l6Ofy0ML~8Z^&9Qj(%y{|=db-| z_x?lA1l+Ff(a~T3y~%)w9)5URQFg}0RrlQ^trGeAtb_icVeCA72CGsH5XgVqlzJ?k zKUZSCtA7ap{_jcrB+HC2Av@mffmH*fX<0HRX{7D%qV4aeKxB(1k?LgX=uM|bpE3pt zlF_AQvuHU{!E%TEupno@Pmi9#fYZJ0ynIcv7EH4llZZlRtV5oIZ|-kg_?JwCxe^ zH8pa1S)(x;T~m@;x0JQDDy7)(^l8oe&e2uBB6DAdLT^b8CyNQyHmby^MI*4Z2Bce?oLN&#_d9#=@;*WG_FG}QUzjcV*~_uz@uOQ9v^Cj`$D6OCgD$R?nUXCUG+Ze%s2S*SYut9M5NxQ-+RhSJYrrYC!c=$psls}tTZ|OiS9sV zcFz3OWpmCfp(J&p!-bXiFG9RA7U$_a<07{yBIftlhvvV?~%*iHX zY9R&QkP}snZO9=vQt+JEMmp}x{{=ok9(fA=8%Ex?D2fRfrvRT#@nE(eK&P#0i_Ew@ zL@uKSPzDY>krSt`p(K?)a|Q|tSTm*ZvlW%3Ht{&#=|&1ElIaHXr(GhJ6c@670)gg9 z0HisYEYhT5M^C?!R6@$q7Lx(&zTgqeXb9zES1=EKJUh(_H@gY}pGCmAhP?<5)Nej^ z_~2h;uoIQiVXkEUf|nO1>D4229(Nhzv68?$Jzg9+ein~SwV<-s3j<|OGhI03fSs9> z4wGJsqi3)1$(A@2^u*|6}DawO2HA!NdW%kONCMmO*WVtX`eF0xy_u)-` z8Ny?He3meTxVD4e%J_IABmL@7;D!oU~-BNzc`B@Pl<=!O;pKh{39)6qA)&+GFqN~B=bs8tHRR^yK|#VYM|>}E1Y_hRN+Os=KO z$ufFvi#?p3mdNK>Qv{M_zQ>=4Y4*Jc)$iH7clX#{Ll(PsEPJ=I*?6t9$)#us^4WL$NheGo|W#dpumq|I})JGt}z;75DbOU zNf-3O>~!hk-qAZmaiPMMM!VS9+5ddSu_KZkjtBN#zoQ1o1DGLY3l?slnV$5NGZ;ap zhQqn8n=cY_f4ThWTMxo#7-E!p6 zfo}~?dEVQVHyI!gV1^VI7yr8Mdec9=V{Sll`>o3L>(>5Rt5!ZUuP9gR3k2B#ktY|U zdvWUz!ua@gZ)w2zj^Fp28A1~D99`e5SbI=Z=M_Kq?gJF$&GKB9Wm!Q4B!WgvT5D2#v?FBH znoiq4+G$grbUGb1nQ5kz#>uq*WIB1wwAD^aG-hh@U()!fj~W#cG4coq0xqvz-U|!6 zz{0}5ZqNDPTNk_$UzwDScLwg|+wY#o@BGg1e2XCA1xxr6mvHPXbR|zfz>7aGg*y_* zhjEI51SsgGLZ>;f8GHVJC=^Va|5ZA1+62eC)GBaCymu@FXSWodA1CllSLtyD7O4OM zq+VptLw!7NkUf;81uvJD?l`>TTSfcgq9fqsmFq{}egA`B;1>UaZ4dpYz^5;1fAI5z zS}rRsJyN= z_C2#M58@GZuaAx@vlkt*6iOuw43EIo1`|xr%pCjsd;k17F5g#0WMh?GB*^aZBC3>a zTDOl77exW4rae$u+XPQ!>-}-jk+>va;G^Eg_HJdSHW3WkB$gai9Kv{FUi`OjxXKZpWVRGQ>j>aX=7kj&bNg+GsT~5FZ-_ z!=q!;<(eiT2YV`8uf;9o5umeo5ULt(9V>tT#0wZ7>k<6ycHF^1)AN}#XW+`&ld%2i zwe%GY2uXgCQI$#w%^2VpjV3UrCBnB0b798q37}1TRVbOxDHnWr?sITBr+j;NmMFr* zmVwvn<)&DGh%{Ej0_Of<2vLc!d3`>*lN1<6U>{Y5em=Tb1>2w74O_Qur+4p6pscL) z2+5%#HIWUSmablijR<30CdB6C(9_vxRq2v6L=(2}(riUM@i9>heDoF=f`Ge(mHG(14JBFXJ~!d`c!drd{XEh9EMb^2+A)9#Bb>z5-bD5LJ`^u z4-EnP#3Ym7^5Z9dMp+7{Po0v6O!c5kj%P3N>`DH{C#SHf29Z8~$u;8@R;DL|28{;~ zDcU!Qw7Gb#9-BA<%O!{f*%^W6Bqy|onzpXB6C$t@?cD=@q>56%8KJN#;#5x7^RgfOVv zK5-8z8ph(Da?Le=djQh^R)*exxJU_HZ)#%|$<5SBJxHG(>xl3KB2HBzpCrH3?A;hF$uA{TbF&QwZFa~tXrZIm z3c?>>*)Qo5Vvr=*I2h!OHVYt6p=84vh8%*zobv3c;ODQ_3%x_5P?VE_M+O6P#@?UX zN$=Zn!X7_efk6oHe91;1&IpBPK@%gjyp3&UWz`K6PU-}BW_=!0tze%-KLVzvr4#Tt z0-FkGcBolw?Y%=FR8&+*CypP5e0>r`gfEBcmM+N8NP<b*e~?Aq^Em1s<=DO-?R{ z%dw$|UX^t%kf}|8mFcOB>qDas)|^}paZEr9sum5X4f!kmn23l{I zm9020lVOQ|+)W+5YBVO{FQGU;i^*s(LUiu2k2&GvOVNUnetF9xy}17+p{THs=yN-6M2YsyU&IY1+qS=^(?lOyzuEvTUHz~Q`R?|3 z@T}9LO4h_eT9QVpsJ$sk)TL_BO+C|s&-hBd}CsBZ3*x~%qt6&KIHB^#^kB6*m*wtesWzeiuM z?s%#|NqS5dA|`!Y`K7SAFb8rnw0;a)V>+YhB_qF*ao zl>sZTxm(TsU`W+KYgZrOxd_kvX*6X?j%Oz)o-|p)SQYy6Lh?(KJrjo)QHCUVRt{5f zz?tExHCe1VYg^3$#JO*NmyK0+F{lFkWqv1W=@ViOX>dMd>yp@DLm=IK!*(^vTIqMz zLrg?Cs@Cmu54}t0BjR%TCzl~x8!u()k{RMO3`s_DE=XVF*yxkVO5gw-0CC2(NAq7>Ywxvrl z583BYh5J}|myK0+@g5T3#!I$hvnNC!qJ~2eLG+hXRc3muqaid`Xm0cm&o(E?Vc(D) z`$7fvrf$j9Z#($erx)Jhwidg~#wxpb9|>@$KiaeV4eaHYi*qws1<)=G*z6KcQh|;m zWO5u4uWT^+ZDW%ME}Z*!pgFmX#qP4P$}T=Y0^IrYyLbHwXU4CdTx|$NUbCYCee)u! z+%MwXCcn$ZD!cd)32`b%KZ?KQVqNr0 z{iAUFb=r8Gz}JcHz1Wuhp9^x0zQqL2L-%6no7(eL6j~C&6KL})fa0@c0_PPEd@;ZU lI;t+Y1cE8F9P)1RA>e5SZQoj=M_C~c6-LNcwg{%!(fbUoYHBzNkmHMZuk}f}zw1HGa30;)Xf&(Um0LBf(24lSM9(!!O;<3kL z&*GW)dhdgo7_dVUq$aNNNze2A=DqJb_uO;O{oV)?4J_cHSisn2=&E1<0*&DLLii-% z1PG@h6aa%>C~~?+*(2nd=aLs({{Rt1AnfLf5?@XTPEHU=}Igg!5w&~t6wwimiun*KO@o!R(8 z=b@8(4B(N{($^BA&HIX1<|2kz_Xh$P85>7MO%tXkCXs8_L9383KJCMVLIX)}z>?G? z#6+2(RjZ-XYT)*G89Tf)GZ+{dgL7&c|2T2(g{!q!J_0;B z6F?+YrJkgvrSjJjAv}DPl}S>TV@AUBFaH9oii*B^071qV#YLg6tp^&l68;&VSXI}d zD2~@d6a%Qb?!~H=dB`G(5P7)V+=+&cURWg`tmZI`k3w7&yJ3A`*zAx-TkL}s@+O{?MvQ(IDKGG6naJ=#|vNTXUVl|>} z%nfT!Hhxf?$jhO+sRLEDEm#>9hD{a?I-CKVx;81ZpQ~rNC}qG5Rz7Y;-A|X3=#U44 zpCJz$r#~9CIwExaZW1aXS1B%B{w%x51NqB5QD&nqK+M&*cA=xC6KhB=j%hD!S`)Uc z%0swLiyh=f;1oKk@Xl7%!R2;KissXj&tS5uzlVA@+tu_)O#@M7{}ocFJOCu0U~9= zrqW~6>icBY2Sy$E>j&rXXo?wrqW8?83sx3Tqik<&cgH< zQH4WEHoB=OkCu~^EYINJBIo1CX}jZ_D=Ne^-(6i^Y145f$C zFPY5PP?#fHOh$5B0axmq(cCkL{AC$|th7anG3S)fJ2-+xv5|S%8{J5syB+32K!JH7#9o z59sI}5VO-4DKb-T)M$`#rEIm6a@W?WRnq8$OSpeo1`OdkvTA;ZzcE-|lO$(tLM9aD z$Bw}1a>4KSi$UFSiurun1wV7DXG^BGZ~67wVt>egeg0+&Z$-L!Ay} zQXmN=nIQNEmx#S3n_)8OG3EAfXBIUoB{6JNxV=6G_1T)Vo=AhR_@>=n#K%Ml<_PL1 zUT+{6Aj4-lk<4JD=6CSG0SLeU_0Od!Qv_`NLoiT>%*{+=t^2RG^{7V1Cy=csM#I%O zYIowv(#_C^=}^~hL*3!$;Uo)UAT`J=WEP3XJnL`UoU}8O^gKxbcveaAk|w=Mlw44 zh9LarX9k< zd^1QVmkF}Y00}!()>M4jWFrs)9^bk)5F2S$&X5d)gELGr_1CxCR2S;nz;0Jul*<`W zP$Gi2$x`MFTx#w@JvGA(`I*paG_pcE`i3D?R8&as9(oITX$deI!cpDa4TsZ-{BR{K z8VV?&!$aRGlr?un5#iXGw-v)tOAF9rt0yvUHhorBZ?BPCM1o z09NNM5h)M_S&c{;mxysCGj1VfUDIw{tZPL?xK3KJG)u*fAdS3ZJMK#f~alnmZhnaTiAyq55ff=r)VA1Y#~FnTYhus}c^%mf?SrFa zhf6SYeGCmG)zx`R{V|bd73-aglQ)ITIBoi*(eX)P^U54~jWODeUuo@;o_p~pLSaF{ z+}QT~OE)TGY#JF)e_^@ub^$om;*M+cS@^Z)80=EI430kwAwEkHL`%`Rzlas;>TFxl2cz404h3>NhdSj7wAtBq_zw z%3A5k2UiLFQp3+XoGzTIZjxLc--{oASpEkF&10?rNV4}A9orfv6aA2 zxTbLG%th>49F3~sDHy0=&Gf<%ber7tB$)I%oW5AAh>Nyhd4^SF_oCwD&Z#O~o(XG0 zth^oJ&R*5nA$Ru0)Vx?S)eUW2oQj-`MaW5w7npUn%*!pd5aTysfBs*&26pb+h3{D- zaLPW8-KATh)2Q+0(KC23N{vysAFYllWJf4v@a*xtt#w%9kWF69mPd!Lf5f3-TPxXE zMA9?R#_luAWC+}eS<45Go<&x2j6^RvGUKd$wo&Q}Qv0>*vQ*}SJZ}b`e(Gr)Idl*z zqY-*)+crvlmdXzmy}MQr`x`AvdCmZ7poesSY0z<4VRza_CR{HUWTla2wVK3ei^o6@ z*SB>ef*R4yjL17VF3yW}EmXV?e_>XNDkVNjq=1y1naj=Hl5N;=i&W+dV6N*S7a}GU z@4ffFc&h9OB+}sqy@)1<2RhLQ4bjf~aRy#fSN9|L#Q0cnvaQ>;|1u@c@+&Sl!b$9h zb4I-Op)oD@72k{>*XRYEi<33pKkT5oP@}f3S8BU9`cmb`r(O-tb+^yCG7t)YiYIiw z`hj(S&P|H_;e;=MWUYWt2kr!Z`(jT#w&%B0%RgO{vslgm7b|VCCF-_9T$EWZPA4&b z{%V^zIx)5X)X5W};=FyX`TqaH0dO!p3JMB-*4*Clm#JBd&o_DS`r~_c|4ye-J-=#s zhK_VCZw<6mQ!JG)%Xo0k;OCbLAo$Q%{p3K%f__Xd^_oG4YybH#A(i>o{GSFeABcOo zK34!Y-uLch*01^5Zv*(66EOD=TckC9qu@8oZ~T}~LJI;svwO==i|qa1c*<`B80x-s mop-wnE`Zq*T5#Yw8UFe5SP5{HP0v}5ZvQ>#=F#3^+;b+mGbcmP2GA;bV7_p!N2vdQMyWRvXi z_j$kMTQ*@q6vSqx-%R-a|NFo1J)ie^zeUk7Xc`Z78rv>R*GmT!4Ea1AW&p^U7krp;75E$|5XR-=y`g3O%u%_HwQLw(rFlK$OA2Ib zJ{5buFCQ zntJBzyo(xm|CF27f9?hosr2^E23lm*`>z#qsTk|&U%uu-SiQ%83X9X?q@Bcr3+UVY;q zKgaT9%mxY!@*LEG%?DlD(^X7|IeT$U+I8cVO)~xh7|Z%Gw(jJ;)JP zRfUE}+;r$fIbD-u@x?{Y!jb~Rdp)P?n)R7!iDXSn63%f(b`>Ar`udyy`~_rPk|RMK zaJn=t_f1@8v~d|0gF?mkoy!0l3I`G}+2Men_-|PL-B*6RYT5eSj8yV^eF7@Hmxo@j zqcOLa_I_4MI#s2))>N{jBnrbBR&wZcI_l{kq^?uMkSUd&)=Deulu@^b7UX48>sdP;KU4b#YUKV4IdVY<*tqn+57D}K zg`WFfn7CF*gXMhpx;tN9wX{$`1%gDx*$^1yE3Ir$LydYe#YDNw>zfT3fRls@?`5Y3 zhFnzJ)JC(DO=N59B-iK|Eu5Q0Ax496iVK~iR@>Y`CuH{ zjW>l#d;N0}SmlMM9@kRhqaU0V5jMh#*eD(xalKBb&Q6XW>9F^at)tsuLII~G#<{)t z=HG|?5urvm8(3b~Os+8xz@lZq@DJFa00JQ)%X{H#8kBy@4-hS;sNx71Tn#!@HF$#pEpP6) z9{~{=!MAgEPfg37J$vZj-kr4OhHDXGjX08Wuuy|RPZcom^V2nym7Yl7S&&boV{U)6 zd9MMK=^S>^TOWKzea>O;`a4(XBEn~o$1_3I*u%0AX=J2{Z0+3?Vo+)M;sU7CbTV@+ zi?hOi?|giO*4(j_fA%QE{=?1Eam9sPue)7*?SZ;mBZ z?Hw3Wn$LDqDl!84l>zZJwRb5^9o@=8$chLJA;;j5px-Uqcl?YURCe#$rFGSmkvTbD z9FpEM{S6Eaqo~dTK0eJg>QUy*P9_tC2R13*Hw4&xe6$Qj9Eo%(YGF=>zc^V5@8O~? zt!ki1WTLLQ!w0CSS>_b}ER7Y#-qSZoO&whnYS3%;ey4J6u9YI-yf?5hBA|nSb4XGa zse5<6Ljz7HrW8POxKS7nD_)H$8l!;A)7#)e&^lo%hYM9mE6uFXFqS&SI0 z6EI`Kpx3u-A?1aqext>LpPAYir}MHbWOwv=PS!N(3v(=#fC6LhRosQRdsf-nC~j5+ zSpYRIPR75aLsrC4<6=K}{4^~q%z=TV?mu>#xZ!be^58w}z_IdLk&*mNv(^Imv~>0; z*C67YCDzIG%f;RdW0`*0p^`JTbk^>md}|ujw|0{9hsPh$%n3073A}`nr)rzfN%gd1 z$$Swx1Vtznis3yceBa?RrM0V<7UpH(%3u}B#QBMxeBOZ$yM1>tj8N#F70bNn5jyvj zA_lZPr)zEcBd2Q6sT1ht#q$Jf1?pt@abQX+8wl4Cy0MUZhf&y8*V;u&adEM>WBXPr zuq07rL>L{fY@~vW6lw>rb4K#4>0k&0xjkOdIVBy2p`aLEhe|6cGcAGU%uW?p@9OOn z#VP3!ZGtMnTHK(P6wL9#5fxP9L&h~kT*ncQMr4__j-IOKV%O$pTQo3~3Y~;*Eh3(6 zPSPqG+LR^pvP4!&tDESdUq4EIKK;S>?|N=l_>2vlwx#njMJ%Cbth~O}2w@ujQI-~D z3mOfK5Z`+o{Z4v!|1m;0)(W#{YuR8GPN>8#*8n?G4=2j18wE60EG?vP1Yr^(KsQ73 zFs>fVr!Losl50sJu0P&)ptOo~kX6^wv*F-JABc;k-vKBQ&qt0NrEQ!4O4kDa;WI)+ zp_WwCvvJB)Wv5Dh_xXk4Jf zIn>$HOXYy-l7egx=vOal3{c@qAxFHYZ$Pv64Jx-R$`^O!Ucm8->L%@>M;=oaE?B_u zIcGPbBuaeJB3@RkUh|~c6uoKjTq{*Jw$mcuea!8~U8lv6Y>K7yB$HNr;*63FYL&&w zqD0}rsgpG};yaN}RWvXL=rGeyhY*jzD1b&E46die`B_?HYp2p-@1;CMJbXqd?LSdL z^Rv?FctwNO*y(t*_~U)g2emcmB5Rl!t8cySchL33?bjFTnU8rOV%ochzEGAg$fLZB zG#`vsC9L=;4trWU?fQncF5+4dtOaK{QWNnKnZ;eo2S};!eOjjdWaV{&s`vuC1tF=Uv6$LTN?4bDc_&(9Xcd7{eZkGRw==b*L*T@1*8 zJS=C1mL(x(lL`GH$DAY@8v;4j*6lDdtrgw0m|`L$z*^_-J$#n$M~vky?;W6=w0JGs zoGc>FjfrU#eL<^j?-5Xux~9%SZ4J735eKBuTUM_AZAMb;(+ehw-Q)3&47f%^`*By{ z3{7N2T-RlBcJ??tkrCl~b7HI)4(T{ECn_5?TbE-JRHTlnb5L7@E?$BIQuvBhDE5Tt zOz+X^Z(N$6DKY@L)M0mM=%fl?M_A+lB7W#(jnCdcwBf@K-uD-$l<{SA zP+Nm8ehmkt@_W|Z^%wMvUtTxY8VX#Cs{#M=VyJx8h)bDr4r*)A#mjI&Dq}}Lnh)cS zWIXF|ZaDN6pdw{VorBsMbnyx}Fje`Lcn@Z4z>oSXq2lVx_`d^JKjxD)2K_%5V2yvp z`0qoLkNj`FT?j(c!gvnlqzr(3rj6ke2mT)hSm3Mbv~wU3LetK8&cc5IkG8pvXC04x P00000NkvXXu0mjf1n>V~ diff --git a/files/opencs/scene-view-status-27.png b/files/opencs/scene-view-status-27.png deleted file mode 100644 index 30eb053b9b4bb475553ec6327b6a18f825d682de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3627 zcmV+`4%G39P)e5SP5(#=XL&O_9SJbg(Icsk#5dn6cM+Xc5rPZxmAQOEXdWxo`CO zn|Y3~#WZ|;BiLKz5nOBR?7cWTQ*3osZXMa*i zo2R*A0^Y91qVrP{t*t6DJ@W^X<;D24^Xn|q0A6!^o36ie&D;;v7z@Gtpg{1+j)lux zc39$d&8meg7a+*fo5<6rRfWK+Y9e_!z&B_<2eLc}MbW?p=w70B@rO7r#;2X%NCXbX zX#)Ja28_TC5%`}`4uA~g=?AoXxAGV_fIRx$fr+>`1qPb?5qUXHTu`b=I>VP}ZmR~6 zjQBQ11!Q0couAO=7ae(hT_0z?L>>vN(WDqaxTf%}42{KeHF)X+gh58g-pIJHw z)Yk5PVfWotPoz3+IC!ezwO3z%>l^e;d?CHjXtdfL=?K>f>z%dAx&OUdvP=m3fCey>F$(cz#c9?TfYB zs`lsSWN>8{Kguce50By3$Ld>1?~FOtgra1V9#)+MgQUV6j9`vZETX6w42fJrXP^7& z-uBjiS-h9V`z4Aw=~vW>Ux%0m^Uu;|o^M(g8<0oTanIjZ&167aN*6Ew;GR7%WTiTu zsM%PKH1c|s6l#2O3U%jNF*iMfa)%ivS*5BS#Kvq{*kLjlU^W>M2oeqw z;ZP)k;qgiMX6NwskyB4!XgV(pS=NA;P6gDzYiBu}mv2r;0{fQ)^j8`{iUL#QO`7|$ zHm4n#DtoqVe1bhL%M#|un+Fbl1Y=CW9m!@mD46{|Vx2P?S-ClIWv0SrNq~PYh`!5X zXm<}`aCjW41_@~f5(ILfQ7_~2Tm++G1u=?zOi|UrLC+IS=g+^y`!0!qKcK4XUZ-Y8 zou_9$EmZgT)d=tlWIzTTCddG`RE)cJ?0oUh@7ub6UBN2E2rSd(^o2HfdOg^jZb6W| zcxEz$s;Uhr&dY{gr^A&gA36qx(bCd|)n*AfHZvJbU}qy3B)~I~7?LR~EkrF7G1f#5 z;Sna^&d|=@h>9nl|2{TVS6`KX4$Frv5j_40kv zp_p^$8v0_~?#bGnY$a5DvdePn9gDpfC?_3COlN~!ZZ9t;ggi%V~ zH;&X}YqAO1NhVyp62Q=G80)Pv5-BHm$E8@c-H0}JyV@U;q|}U5banK+QEFBaqpI}K zU{J-ThaSbgeS1L)yp%%x`jW(;3ymtAoZ>$H@loQPY-ol*5K!0X6v<^Z;FHl=R8&`> zC^r+4XbgvbcMiJoF;r&S(BciCZzhCN^0s9GID3-EwMH0icJu}m6c)NrlAp_X#B;ra zBRJW3>D_-jeC)}fZ+e&~mtfT*piLe=d>E&W9>o23eL?IJyV~nu!aIexKKK;(XIY_8 z6x@0Xc5J>=czt-ni(mfc6duWQAWH5%G2(~I1Z11%DH)>h4~JAtP$Dw4bP|i)1UgHw zU|gRF9m(j9>J7A=s#_q1Fo2VsfA4)n`#!i2_x%)Y*%cOI_HJWkJV;A4zMAQL%Ql$c1|1xv#Ge)!7cTP#(Bvf+$atp(igg zRr)FXs@;KYRb`6PZX>moaQUeICBK44Ywv;GYQbzE$dy?!>UHE{n;Zy*8PwH1X+MPqVF}L#LdZ-@ksL`> zPeLL7j1dta}O*DW;A!X(R}tI?r}NLO?uuo6+op;C%Da9as6@6DcE@TX2ona$|o8w zf(srervO1<*?Is=;Il^BHc+I=Q;Mcjv_Ka#czPXuFg&e8+3+>;0Wbg zSz#`E21g^~g?ly-K)w=0x><)0$3uAZfo(`InFRPt1ir@Q zMC14@!r=%CNM$syGeE))y=yf-YjqO{318i{HI|<2&_#%b+TaKiO`{jP-1<+OJHcvK zQ(evxktq<`WBiPu^_jL_T%=;St)d7fqfs!VdvFX=U0t2}=7HC+AwLT?Ya$xk`r!5X zP?4yE(?|xz%y{5Wssx8moo_{Spdahgl5k>p22P18=_1PTu4cO(O;cgqv93UPT&I)K z*5jdE8ph@gYZWp?CaF;<;wpJu#~yc*XZ>>loNn$wQleSCqc~rsVkNUIpKI$D9xu+z zQd@h6q|N0;A}Z%P`thx&pF_MLJo?DqAKNVn&w404JF{%aOwi-dNC4k>XeW75#>eN| z5b+G4G}Vg#9GFH)0$?L4)aV&fC5-42)RN)?wU|^z=={P>;cZ%@hm2_I=o8e~wWR_U zQWfr6wZW7LQA9s!YNG~DQ3HXXv@SnKbgKN^$#d<{QRKRZuRMF|!(*bcj2l3kNJowy zRU6-W6>A+P9P-TIYqd3k^L6C;>%eniqMa8Ckg~RDHPVvpa#OpTI?fany0YVnMvne(Ra< zNL7`U%hv%{LE)C&)fJy^ry_N7$cJ6`-i>5ilIUlA-Q8GEp8t=2@;rU8*$BLmsL~gp zeyAWfo}8=}BJsS_xX|fF0r6TVODZV_$>Ex~9CAds^&Xj+BEUVUE-O;|21ljgiAj`_ z$5|*&oNdJ&MK0RESM3}1K3DhA2S1_p*=Nfe@a5gR!;cj>jV)B9a>~n4O2B%kjGwM= zAhk)x32MSJNs;Uep8wdVVtZvNKc^7rc;YcQ5}~MPbVA=dG$yKas@*D*ly}!8kv*U_v@R~$L`%E@kb56@Adg{ys=gF2gA?&_JgDU%%FKJXMpHNj~+dWyVEVkIqJuK zwq)!g@YDWT96xay53fl@!`LjWl&}u^;Ly~jydVd5iy0rCZjv)nomgM!QdqqxIk|G` ztJW96m6a~q5w7eFE#0EBuc6|_lxe)w$;l~~6|O?r>P(5f&XRey-5vM%JnY~9SLDD$ z4?m1Qb0y)pX9|1w?1I^7z{~HSz`ZF3Oa!9n@Xn$%NhiRw#&cWib!MQHw3sE2e&L~U zuaRY~c6&8Z&q|HmRmo%sT#1>>ue^T}CAn!TedGv_b0k)TFl9uKVu)>%0u- z-*CbaPV#;tM2}h#1Odsf+oXqi|F)!tX0Zp9)YUkyN=jwlZ{3qI4|J`x9 z2*eGbd2c=qdWBH;#Dy&wl(lsIte5SbI=Z_Z9x`?%id96Bf`@83S-+3>BOc$o`rJKUo73iW$K%h%~o(gwL91r2tc?mG+ zsgh1(&Q`ML|0jif)8^ZnPHNitVx4LcxDD@Z0$+Ah;rR)H#~r075x7eP*g?j-oO!5^ zZy96{rD?*;g@tQ&t$DU!bGRi4pPoLy=lu`=@l!e?O*r<@e+ugK<=*f6{GgiG78d@g zaB;z=j!`T35oA3BAQq=h3v9>yf{_XE}plUJ+9LU zxH;lN$;Hda%S@KSEx{xb2zZbCo4OlfT0%5Z6Jiu{9FarC7w#*yG_h(5?}0$UmiG_n za|_o#AJB+dhH?nNUN3KgbfLi9n5pKy5WuD@2TXdCcRDVtQgM zVxz)wwxS9-X^998o(4&hC{j9jJd#{iRVO%HZscdBp}DOK7p~NPKsB;)G9sE-HH8`k zHmv`_t`!UCY)*`eL)l0k{^-h73Y1ukx@e5Z;+_jwVmycRgmACN+0*?)!>Da+!SvWj zG_!mqksMTW%}IBXEd>D3Wii`voSos03b%;1 z_HOs3Yt5oFM`m&Y9U1K-*PGjLwz6jL?hp5GB&>=7`fR-APb6^U$Pt`A`YBdD zGgrxNkB^5~slg!P>a`|({f!mriP88@ehwT?7X~~b_;}=5l*pFR+F;gn33?F=q#nE& zaugo7b5zK3(q%LlaJjA#4Yp4B8FW}WKbOqOdcx#A$I;`T|C~IFRqNJc#mZIuu9tv* zJ6(Lbyu2)WxV5ez$|NH)G#C_ajEp!CMujJk0`4a5U3OPxO}#$S5{$+9*$6fV@^C^} z$TU}|S=Z<*`)-N=wNXTb1bG(Bnjr+4 z%|cB>i_W0eAuzySAwW2#Mq3AC5uPD?0iGe0pDRXSNNCd9?-%9qA>r_$Lvn|;0!gtE zN>Qrp8F2s5O)9E3N*_rccDRKZ(__KleTNjE8ya!q?;1el^A~>Gk zqlma;Nm$ZwKwcDV71ER96tUMPx7FSOaJ`ufqTJOtD9oLi3Uh#ox|Yvir08)i`NgC| zA2!W@d!Wg{o|dS%I5bMWbJ*jT5U!$qf7?vY6iL;O`+GVqNbJQm3B->I)x82p#H>gjJj}f?T z{QV3bf0Lh1CCN4^%~0FahJ#0rLwNhQzmz#mxV4!B{V^*&8C~{XccryaoRgW1C@L^! zucJ1})3dsv1>wPFB*#bN;?+7O|J8QLMtJgavF~f0jq?%nZjXWwA+8U>)gw104z{jdB_dg=iPRwsaJfCoXfXNTN1A{0jt3p~r6lP41R|>tOrx(-? zsn5q_Ft!NqI5X}Tg-Pi#Df*F|L-5#bGzpw2vi${5=OignDXVG3mX}|p6=p2Ce)Pkl zw}J!xw{h4O&Pr4ClJ}jLuQeN~-O2PzSeTokv{B!v@ZMwZ8^nPx&ckFBR{V*|X=c_s{QOF6Doqzn>Dq(yP}PoG088qR&ZBf`L+mN$}+CwD;k3>1By`orut2 zT}5>R5~IVAN_!9!aLFdTKDZ7YpaC|qE2)6hyB2V&lU&J;(57S9_TmN<c%jLR!t;rA@8HU7|NV)jp6(NIKD^E_o7_d@! zM5@x-fD~G>^*TXrXzdh+XKciNq$_z&2UW%$9z$TkPnoU3rq8}DAyCX{d#vAewAK!H??<(*IPQkwW4$_ zA~Pt3pvIMoe>&6AspD-{x;<4CkM}~TIS6NW_14diAKj^9(Zs4L^dX?W{M^b_+Y=)# zZxqZ-!8B@f)eSbJ#z&&Mz6G=w(axVJardEiEHskGxwR^U$=hoC4GRiH8VxUQkzdsg z<2~InxYQgUOBKtFqh61zw$-~PR!yM>0aXd7R&rG6_DGr!nMpB9EpV~7blQ#F)(V%* zM<@+Dms?pN)z_4|p-mIJ51vA1LWG==6f3+ryc+#FBjQK8F{9%In(8Z*RPnwv;XxCt zrtl5~RGr^_ZsqGKF=4;UA1ijZ+cVNX>@f8W3@H&BiwF-6^0);0@`CnmyF17nC?-XR zd8iTSKe9gsbJGd5HC?(TRa6@j@0wUOh4(-}Rla-$6?>Fr`|R{Y5Ki)b07u2q)ZXLA zbA{)It+NMN$#LLyqou=6bzwk-^@eP4kA2e*~nN2;hg zCf+r%Y6>3#fr-W+#ot<3l`iopls_&VPY66N&#QJd|GiGR#$RT=`;dD0+jyTWg{A~} zcIYh;pyo4WfUDWy{bE1@Uec!=0pApwa^N`%{{s#KpXNb?RmK1S002ovPDHLkV1njs B92)=t diff --git a/files/opencs/scene-view-status-29.png b/files/opencs/scene-view-status-29.png deleted file mode 100644 index eff610666206b1692cb06fc999ee0a870130b692..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3125 zcmV-549fF~P)e5SZQn&=NW!xclPqG_psMDUSm5pj={#n1_Fd)97sx1 z>~Q306h%suHZ-InB5KvBl^XSD73J!Yqk&YU2~pEr!~uiLVIU1LmoXUMc-QOu+QYlv zd#3LaPJ-0L6`wRav$Heb_q@;hyvMhkNCUHY$YwEi2|6D&fTIz;o(*RbPK0pE zCJbPpXN#Q1$(Gdf|0AKvviYIPDJq-ESZA9DuEBfFjmg%{hU=RKeBWNW>46zMzz9;# z@XQVUd_^IdSKfx0Ed|KmxHIjzlmx;>6K27cu444}!{jjrB7m=zon6f&@-mAni+ zV`(Kam%fS3DSFLxJKfI5C71=Ne(Z zWXJ7^TKI{@QzHSCloVoKMk;u@9K%jGnmc>Z(9nvxY7S|7H5pA{y&-fF;L%V7aTJwW zl9r0XUqytl5hSmSlCm5j5nG@89hNRx^4$$s{mei14Cmdu-YHi>6lhjrNYi#4i(3AQ zvScj;x>q|Kz|zHq$Rmjmc{ttJjtgy_u!uoej2euP&YN8U1W9H|N)GXKXb3FxJChI_ zSMRczDeQMwn8RCN*5A?DPK(m`##!ir%^P34ds)#IOIiSD{2lm9D353vME2=7aF51@ zbc-JKBR*IbYh=F{Q@nMi(CTCz)HuJsJrQQ z8XeML;6s$dob;hoC}Jn>&m^I8$|_|g^Pgl{WJS@f{&=Ha9U*0n)P`jw7sprt zR+Ry3mKGvLtwKKq@4NesV|knksj(`YANHZ!8^nB_3^5cFjO8n_M&Xcb3sWaDZhlzIa$`2=KVTacHT z&H`0>?owADj-0&k(Fc3?Z}q!fz3k?7gLv=Wy{M`B4D0T=OIh@#i;7hDa z(Lo?DSa~g0-@Zs-uW8Ym8|qka^` z%D@wg42q51zc+gu9!!&orRH{y=OQ?Jz7dVKJ`~-W9m&g_BO7shxz4^p%t?%c*<_IB zrB?-8&j4rZ9pp+Dbw|EgrCPgY%?^GT^p;{!ZtFgb!jRd)XRE>zqd97N3ctflpm&;jjNG@h>bGSW$L$&pMF3F&XGRbk0#q!eSu%skNfx6?< zL%1hdLk$jx4{UBM(s3{;WfK8<-Xq{p*_+@r`th|dTV;!1z}fi%%;z1Vjvu?cCi9jk z3IoHA(1^<;&zzIO1w&zIREmg3O*MR6Rty)(46XJ)?Adz&+{=IXjY!~O?e2$;Dr7-U zCewQOT#Jn#bh?nQAVy;p*kgC&k%~K^)u>V5YDN9&^SIMuL>uMvMyC(OdbwmcgUITS zbxgp&R_y?%P)CWo=FG`?aFH;!Dw{-|<0}mGws4DxaaO4r<3uQisQ7hKrJkFb2xF`k zr>F?_QM@h4PDguJKX|V%05fSlBgHJaxay13;Awb$J_&rHT8^rIA0FMXhS*R^;4cvP zH5L<24tfy`hOpQmmn1W;8hFUt$vIiq1<$Mz2|Rn}PoaE{(-%P0g;6Frk>vFvLp}%@ z<$PI;42mozcK1=d*SvyN3$u`FHcDBF(kb|r>o(xMU2mf>GX;8G3_{ey3^;~R6eE}9 za5|*IBjqcg;Ca-Xy@+t94P^xWaPKHg6pV8yMMm|`K&cG|G&zG8*GcK-&?%(vH zL95wrC-%HoQw^|xDmR76)ZqL0c)o+e&Tp7Wt%u0rY03OGRSb39fk$g2PSE96C_-O4QP+43N6f zs;VliOw=mJs2_LhDlZwe&HNkik z`1Q_G3~boA5kIxWqT24nri!&tD;0Qa&tcpZufUKmjAn-y`LUN%CCl-wt#z1^kWaao zF;7RZd(fe5>l#3L=@OEjjv70X%A~=v*a&ScckDTWyz~T-HaXl2zI)-O?d2T*FmeZ~ zVehDuq%!HXD=w$?z~hfSj*oZk1h3aaOJ&Doy5r}`Yb zWp=l9(B*lqI4_d|U8PJloBTR@`Fu+UVyO^~dqnEwB9tOe)Hk7j*by$t%iuGT;{`HE zKF>D_ovj*u_WEbuog|ejP|xsXA`4N8mVNl)M?!Vw#}G-!tF!_xI{Z)zK`4oK){isb zXX@*p_PLxR(RtV2z3%rJNv7u)=2&R&3SvKoc_d)(A5pQo!rFlz+MwrWrb!$3?g0nI zg#vXgonlMR(6h%6RlgLSYo_y59+)s-a%A7W;*K{9(h`5}3PzBw;_y}PwZN}l?BNGC z|B+((SIZXMB6)xo1GU&9HHBPK{N>8&rdT+3u0Z-!~DQSI;%w_itCXe2KaPy`Y)JlHq()rnHUmz&ZG%^*%)ak~KZ_Rjg&gl7D84!Kx zyPlaLl!AUqn|fuR!?XR^w~)$oYyM9Sm=45^+@H#T%lo|>ne}^q_oe~gvje97V~e!L znschTKm0MBgk}YJM(-7WEwbGI!{_`;gcFsRZnGwR))_EaLbDD$^TmGw({d3B80v-i P00000NkvXXu0mjfdX@eE diff --git a/files/opencs/scene-view-status-3.png b/files/opencs/scene-view-status-3.png deleted file mode 100644 index ddf27c2db1e4ecafbf5bda486daf7c5084c0adfd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2169 zcmV-<2!{8GP)e5SX*oq*BSoK?CkFB)$98WuOTY7YsUl%CO{0f1Ej%g zk~V4jlty`?ZA2P1O{2DXYK5w*RZxXmDM=rCiJJ5lwaMDxhBgM*i3l-CYcODNg0C3g z?OpGEcc%Z0eb(zG#&#_`eb|w9&zW=1ob&zP|NZ|tGeGJ}F_2^(uh@>NZ; ziid?&Jj)+_iYh1h&3c!sGJu$W5lqlAomfouht?asx*l83q6F^qH13oBuiHX=Jhhb) z@T*EJI@`pE3q=-ajKasPN4Z=N{E5IkmjvjiN-P#{B1fq8x_HAnRhIMsF?}yFeNK@H ztfH8S;ed*b+6y5F1CV77H9&o4ezRWXay;OU%LMof2S#8A3H&c920#X?%`V=zo@Lwv z#OQZv;dTPn!U0g*i^TG?oMoodLy1N1qcQKzkC`FOD_zu9#@p7j)TotCbM3oA4zCnFo;e9J9_Z@O=)iL&jMaNv(23<*pQG`{; zpl1pugAw>C#3HhSfsht!nDhmodB63Y6HBeZ2w+k=WavL)O_WwKNvCHv4it$Dh^Nw} zCg1g-tJKdF2_O`SU}$s# z9-kj=9oL^Z)zq}8A*umy(sn?b&}{lv1Cqc#NkD(g0n!*%BCk=~ao)Cgxrx=(SDYx8 z#H06QNN|V@y&h6sdIs`~3gOJlg56@mj6aAQH%D-_XAlEJqsSs1W$P&+Fb0Dt;HE!< z;joMtc|NA7X5kx6Zucw6>#7E9AbIqYJb2%2>5#_cQs)>MFrWDO=()1;fqIrD3MWb| z`lGvYEC>>d?GqvF*s%qr#RU*`I*d$e^es znUS|ZzgZ!K3BH$Bo{T8C6;ZzZcBA{}e0d%N$H<5m7)?g(f0cm#nGaY_d4GMw3#98~ z_vcwCVa0HMJcN6zwxEn8A`^KRu6&G3-FVK^NoOpwmIrb7tRMwn}0$fTc%z!LeI zgw!=>^)SD>3RLZ%jxwB?idMHYx!+ZLH3QU2(aS4OhYz?Kwu_9tUYldtTx8XwV6{q3icmV&I zm_~(Fz#fNAV_5~QDLWZy(me;>8&Gg9Aiq8l!_oIoxhMFrxCeN{Qsq6Q^-NJ{`C^{6 z#!0NdH|)dKom;WFC=Zcn42|!82;Jxiw&&T=IT^qWZwTcU0Tv2o6|l;>da3n0G0%Yy z1V0`4>?{cq;th8y54aktNvmgEg*Kf``t1=dsN1zoV|{3B62ECWi^qx`h!V1vTQhJP zfdXlEiwcqZhsgb76o{$~Ih1vZOpIb=;fl)z&YxPg2M)R#zJHg+@Z-9C3%bT;U@Ixb z){>3Hq=Gimx#z|W>@0Aglj5Mq8$_i|0L6#KaxbxX(I-D20p8fB_dh0s{b*d#8+=TU z9d>o62UZGvMF#A5HT=55W_)r_t{LsOyeO;MMv}=#l)Uh2;~8v`6cpGb{C&WKY@Lh( zlMrY2f+r^Thf&^o(yi|3@#_a)+W$R|EdEe1N(Lu+qJI&M#~)avvZM#<>Kk6I%P~J* zl4e93$zosaZscY+s<=>gyYeE)34a#wWMm(Ewt;)s=7ETVU4Xt$q(4e|SGECJ7zad(d&=BKA8S=%##r zWh#K}cAcheE{7h!jDLFNyo=3^i-J!E^S|6Dq|t?sbMQHm4$Z>>Jn`iNFd2;+_)7%- zfHNH*jQS7`M^Kff(fI?asvOizG>u(GIEl@tFr`ThMtgeWPZlAtt~Ks&NM%($uid!TJ$(YLHRXT zjdlq8*GE?nQNd?f@g{*UGXeIwjVTeH%Kj(5*c4?P^&`N#TyX=4`CVl;!*gZn5?XJ1 z@#vwwTEKol=NT1zg$lkm0eliX?Oa1`qfuo=2f?IiR(u-|V%lNP?#;EJW5kQfFH|DK zZo|g|w{WfFGG&_#w2)Dy^vywIK4F>o21I$hJ1GC5X-0V}p?l4%ngMjGJYJn`Hu&j0 z?y+a!;9W(Sp7G&y%Q-yqxh$L?@xe*~>!2479o#|!#@gL{#;KmH}dFd4}NL#6GHdX$=0Otg02GA?pE6s!6Z;Ez1EM^HkgCi&t zqY&xcwUyYfP1nA<(W@QsSu@Uo^D@2<)HfVv!EpGc(ha#s6pF|ii;T;YK}d2K^r-_& z9c%f7>s`hT;0tfNua@OHtS@aa>yJ!_V<<8*T=C!jBEiRh*5z#ma6qa@i1_C{G5m`1 vdi)KD_ovDf11Sbl45S!HF_2;)#Q^YME=h*-U2L6s00000NkvXXu0mjfJ2DHp diff --git a/files/opencs/scene-view-status-3.svg b/files/opencs/scene-view-status-3.svg new file mode 100644 index 0000000000..56b471de20 --- /dev/null +++ b/files/opencs/scene-view-status-3.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-30.png b/files/opencs/scene-view-status-30.png deleted file mode 100644 index 63ce7d0d254ec549590fd8a9d12e5f57aa0d250d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3096 zcmV+z4CnKSP)e5SqX4d^%?)Y-M5=fvN<;Qz2pu_2*d;wwMFQlAY;&i z+ScieW2<=7s#s^*aaw7|PCHgbrylhLv89gIiin8d0f}-*2nhl?A@_Z3PLj>;zSr-2 z+5fT$8;nA=ndzI^{NMZDd*Ao_eb@hcAgOd<79YAbWo&wPh5W3TfeA|TFZ3Mui0At(JBX8QbvJV)4E0w1q@Tu=lE z_!E$HdPy%^>AdJdz|HX8bMQ*cf?wvm@;>)gNZ{tu_37qyL3SZ0StbFmQeDmokwm&x zMMuy4*V?i-K5>2yBLQ&A`X*ifYKrVJ)y0iq;-EnA$x`>_x3*03x?tt5at#D|`cm@r zK~*8Jsv1BZ9_1S(kB3gz0!0yEN9kUGxcG0Z-uT4%Od{}}CryBV319@amB2qsH2@;W z;#K0_x4C)hX9}!$7hp$jg!11(k}VH(X1y%LPCp@e7Iyh+s9He=6i&E9*CU zU0?K54*}Br=RfTon`)q1g7+iZ?I{nb_mcG1sV^24UHAM;fBC)ZotHP-Z~N(wU#0u! zD4P1tH@Vho;DcihtUKhOPNc)z%M4hUDtpEX@_02V&frv`F7q~ddhPiPtu8A1ThWrj zb;yX$gkYyF<}~dRaz$cQ5u=)L_hR-~7^R z|M(e+C+DoULLcWo3B0gxRDHCH+u=m}=7J=!w-V6Tgbs>ADV=X6*^@#hR!)`GOBUYA z9@pt4oEdYVbictA8{r=T5YJf-&Ca$G3r&f#~bDJ zq&UPyN8mttC34e}5Ec?domZhq>ELoHYFTBSWOq1Gke!B>jvgF7QTrOz$n9jvCy{9h zFtVf&VgyR76e?S<-hHR~Zi? zJu$-LarX4!$S7(XTQMgt3JtBD7`2b1Fee>;MuSF*3!S}I+tiMVn)=Ppy|U#Fx|~Gh zD+mnVd+5f1zs6c1(c01nwH1|rb^@) zxOAw0lE+m=ag2#eg-+3P%Zszn}8xdm_e=Tq^Ybr{b^ z`53}2rv0d|{)}qEsd?N)9w!f>r+*O5ls1V`VTcS5MR(tTy0`SWl%0}*tmH&?BAdnO zmJS@KsNVd_>sxOp0-AwISf{euxpOD>?RpO@ue^k3AGo|c#7YeY87EIQk6YyDx$(dK;g#N zm>uC%cwF4vO*(sQj*9AfeUv2xOA2xjVh-fZ3E`nZjxe*N&o|Z}LJ3fhs#HwwuEuMN zWWqLk9%2-enqcgfWZDyZDFW0+5g8inx@=yC6l^w2)eWsWgItgXKg#m=apjwWi#Sca>OAYi51gF|nKDs)lXq6(KcJx;0)C3y*7U zwSNd)ZzhAR_6!V5m&{FtIlx3+%hSQN=y5Ii#im0a9^}6v&}3jwE2Mx&3oYL{Y_`E+ zJLRdIVU3qYC>rcdOR$;auNmfNxE&s=IW3v|jIe~5rM7MxE)VZ??3N0(4}=Ni;_pOi zG=}F&&GJ0t$Trn1pP8^pI?1-u)oXL~4Gim(6JjN<8-G88%irXu6C~Ltr5S3QJFtD{ zZb(o4;nyms3AZ+Lpg-oNC!@#K@2se4lowj|USDw#gCUVsC2i z*4l4qP#{WA)S{!uhTPOdoNDQW^vB;kpjxBDC`rgW1oBvIBV?LemR~ks3zc`9WADMBXoti`) z!T^WUrR|)e9R`^E&_6VclCnyqB}OA-PJ&i9U48wac1V3b0mE@?@XUDTd805bJtjpz zWqbrKTN~{J<5<)7A}-IhYEdbxZp7X9JxC*rJGp*z*cQ)A)AW+}$B&5sHHFH>uy-cjMX$2KsGZJ&MtlTlU|WX@4DlM^UsIaVq*j;z5kg}*evW2ur9wH{A*Yr3jPYowM6EfPJOM6`pk>!$NfBRSigr2}df;K}^7*n(*-O z_2>d^V4}4Opi@_0z^P916dS^tKg9aGZbD%}0jJMd+#&*#gmkfd#mYylQI-vha#B%Q z--d;h@8b@KIwCMx1HCC)I{&@FBpIChgiGZ-M;@VZeqcQ)t*Iltd=6=iv zbqIA^$x&%(K{n-jBDY`9OvA6z%g*M`9{F@@7r0imu0>>ql~7bWQt`%kI=Xc{%_`@n zistctNY&dh-dlD5C%bn&EwK2+>Qm@NK)ihQij^CZqAZUT&P_oOwYjQ>Hl!v*p{l+W zG#Am#pQLd2p>`}alE=BVYJ|z#qWy*k2O^C&FK&^a*N)<4-7+{tj=NLE(zMItaba7m zKC$`~`XC^baB3w-hi!$;*HPad=JwCb$gE}J;#(I&_wFk}hZaMW%Z7#h(cEEmDf|Wmgz=l#u6dmHjC(H4Nj1@Dqe7~5{MmS< zvi@s(T<8?5Ppm$L7eGK5Vg0000e5SP5`cug9Kkj}3aQsSn5Km9D%`uz1gN7w=qK6Yf| zx;#L@UjiqSb8tBzVdqD`WL@+eS5$t5)@Z}9VjAC7~YB7(PtaGf^3QyXznFqIZC{sP~qgtr8GBJ0!T(YNnQaNSWV}L zrTGy@w!Uh_^?&_zWFh;@pN|Y;KFUNg>?lxTW~w6CRuJRW^m00r3T2rc#Pq6(7Gv{8g@tbw&d*+_CX&8D8zicfF_R^Yp7|ktiMuq`Sp=|NxC1E zF?*c6ko$p$$zbSN+RXO2exw0OBpu7peL+J81XJnA#qTXD+87rZwzgnqHlm1iKV_)S zo?aY1cNqhH{m2f}KrIufYJ1TqQ$f(_kv=&AQ4vO{l}c#TD!2y;2PI*T&xh8|9ynYB z_;7E@`U_>}MMK6lU{i@(_)`^2hePR`1tfv}1p)o11dzPIB(ja>UY6#hBa=$e{F!SR z<1!hC0b+COu7gkocuWu1AdH-8b&%>z;fPC2fH^i22Avkpfk8C1bfC)8jHcF3L@GH% zDJdZkfhvUzEdxHZdwB%N^8sEEnwo5D%g&$Q#P%KK0jFCKroKnbj4&k6WLoIx>Tf*2 zULXTv=+H$5FiXXe|HPKhKezuOuMJ@tnV8!2P)Xg-VAOT?DeI$}z`lC`RLxb~DV3^-_K6UE8kT>E? zq7IqC;wYr7QjnjuhGmg?xl;!t!VH=KF;`yQfcmOB%ptkh2RyK-jaW202O%0Y+9`P7 z-+LT$!_|lnRpVl}8_g~+vh*^9P)snEM`M+CBiR^@g4M%ukui~|t*(DRQ^SY&1#WrM zpn%!SAH=FvD?u4}w1n8}qXL62Mh2&NpZ+)~@Q#$0!|8Slx5#PKrgJa{!eMO^7h) zkeV1LM$e_1E2y|si-mDIRChTMnmz@&>63{`0mn(_j)n%ziVs5t#et=N5ZR$JD2PP{ z#Y*nqle!)cB*=sXk@cK{3*h|4N>p0gkUKRskdZt|*5hz-jcuKn6djI8lTlokUgfPV z9h|kblgpo$20h8s-tWThqo?29ymi~7%s}po-MfX3nldECM~N><*2~FiR!C|a+iGuY=Q> zXg9H%V}a!}VV<#uOG_Gx(cqq9s_hr6wnnKCy84{lty5E>57B_doiv!d`bo~}xHQNk zLbcE9G)hDmLwUJe&Voa9F>{;U;q)9TEmx$I400)x94A@KT`(8sxF|7DH-CNzOJlXv z;BffJ>c9*tA0`#+orcCmCyzq~Z-dhshG&NESRV1^wYE0w+_N9ttAG5hK;U6%Zik*KWOiCI)4Kmc zjaAXv+lNdgF&d)8PMZS{7u^Y+R)g|d3(C)4#GU3a)KNaK>~$m0AQug15Lx}P4Dk4P z{$_AW%`o_@53K5Q)WWP&RSA0gDRi{fa5IQ;R;d}|L@2tb_%%|co*W;IuuvV&P!VjS zc$=1*i29~>C|qt2B1!8>agm~ni%*<|f`-fO7QshrOH&Q8E(qPV#naTFSSAXXdc}ioNrOFO%a8*%0)86!5)>ak-Er?}UtUzAQur zWr_;h+bG^kUc>z9DTt2@6SEYhQz-6OvK-sCzKfjXI2iOH@KF!bVedk2h+LGzS)UpY z7tVuHp+L#`%kVc^Q9$4ixAwzC!8nOhq@;HSN@Fyls@IF@dL5or*W$0wq~WbX1due`Y1-iA)25SCLd2+yatQ62;2NC}oU`foJc-g&GS|sKu7c zIKe`B+@1bQY(pB^K;Ipb+ay0s0DL;dLs;Wpv{)}}IC^mZE0S8IqRUcqwI@a2%#4ES55oahUmI4gm}DGTC!1{mS^^ip_E~B_Y49 zy-RVWxkIedkw!h(_kxoTL$xmzA9&NyDP!F%X@9I#M8>SSk zu^TNeF2)_vI@JL6;|@bO78CeBrwa!TpTvq=B2n7mf}R34j6OJ|fXz-xfKjKx!INdO zm`D?{Qq4RiFP4s3u-YP$=Wp+l*X<#^WC+D$RY zq+HCHr=PdE)2^y(>OkRwStLC@HFm?FsKdLnEY?GxmYa7TK}KSfKp#2Wi|%{ipwG)m zgWX8)H~?EinJATs08i+F)sL>mr(3r`VK6{PW!plo&rtZGpv8|8`wL9sf87wEsI`*r z&$QXM%WMuyXP@)Ayo_WDbhRoz(ln^2moL^dAe0Kxut&sBErWO)_ZyY2q`j3h-; zYy?jR$)~vcp|>7Do2~rm_eV%&A_K;o55@qMXxWY(pYR8Ee+q$gJYUD-vV9O5-U}7c z&iZi%{9JkYlkUFW9%-?~cQ5%vQjF>O>1k&Ab_KB?!aU-!wfCr5U19A&3w@wxCMSp= z?#&%`iVG#mY8r)_mab=xA3E@gwAObW$MZnYfRT}X+q~P~&Q6Gayw4jzqMF00)~kV! z-Rz7{Op`*w}>8~%|I=-KusYR6ERdd9TW@4F4XW{eXb1$_U#P@=h(F-y1r%u zm@#bR<>mdRvbOH;t`Uq+w74|?z^aumYE+7Kv$Ik)ZjYCzWn^lKsk4<0-PmjzHNe5mc2_HK^Vs0*x5dg|4;!y$M9CDxE-i1fo zI}mAtNQQrjohWGD0nH#t=R5KOVYLuc=J!ku5J{?mm^}sU+EFz)Ofb1$QSlfcTeXIa z3mpSQdH`}4<#!N1?4OOWL75h5*V)D25r8;R|Ezv91Be6xNTi>f>M%>{r30fO(=rYQ`o12j$oWV>mngq3*$ zEMOOAFYH20SO~(PMdVe$9L1Uw6>|gN)i2wHI&}!G;MT&q(VBN;ANd0^t~E$B%mJ_b z>w$f^JNaYj;rUgE4i<8r=~b&;@E6BBM7!a8glH6XqZl{E2j8of$vr6%IaM#~G{Sj4PF)2EmJFVQtJOPp>GK{! z#^Ufw>Uew(F8? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-5.png b/files/opencs/scene-view-status-5.png deleted file mode 100644 index 63d37f8be5b5c20e1e45fae96325ee73faf5ed43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2097 zcmV-12+sG3P)e5n0ag*R~^T{bL@R&eP8j$t%7aEv6HlMj@F5fw59PH zS|vh3sHqTtB!D8S0zyC$r9vPf)Cy7wrKuDU6)m)agxbcXiR#30p-Gb%+&E5ZH(9T5 zJGR%m-g9$RW>e1kV#}Ktdk@`z0BvB0x5y(E{aZyeQ%O z$ypPUiXlmT_T7g1U&RAibpQljMUCq@Ua+bP^-2n5_WTp;J6=Xjwgn26T^x&GdwCg( z$s!7gce(QuTRF;vY=a5F(M6e{jW+4X0B%hxcD0@B`<8am+ zt7vYlf1f?4cvqb+P>Wwve$E;k!`dsy@u==5@|||HkB8vgxB*Ysc``i*vPCZZF;PhOB4C#&u3dJomL(GF&x0=ZJY7A{A@@u84d4W zfiOCP2ea+yml}!Yyv;*Xa2kMI7LuMg_CnZgC%|} z82~BY;>xk^$~EiocCQalJ+TKClM(G*ZnR(i2z#6kbklm?=?&pQyP$cS!81F!g9=Vo zoq!_JL`aZk0%1*lLit4RVOw8mF57GAx@!P&_!K>lcG(Em?yb;?BF?p4L(J2Ik_;P~2mMIlXeXsrWnh73iUL{~e6I%^2>z635;>xR%ti35myp829Z?Uld$PoH1n*hpYOPInH4=a{ zbOW*BZm1F+25H_got=0BoQ z@X~K9+36Kd&>fob`TQ!gip$=CX|NK$4WIN4;p6se8hkSy!iw$!&s3L*+#OVLW?cT`DZl#7x$fugD3HUdEUhOaKTo9r zwgRzHNvOCS+eqLKECtVc*F5!@tL~+mEX%VQ1_A$8P1s+(9g|Z5ym9h0_N`CH#gPCF zr-ZfhHYf(Z)FEpQ{qA?p6VU=`ptFkt5LqBIOKKa{UQ+SJy=fNG@!YrWqf_JQUN81P zv6Ig93jTEF13X@k3Xh*Y0mD&junF2~69@14lsjh}!1Fb|JNx;IF|vxd605}9Exu|2 z&?npRcl!O`5bbtY%tpBTM^G%rAkw#M3#ng~B53WHRsLJ>MF5ux{_t`(S=461Pt8KC zq_*x0TsRs%RNXT3i?+^!t?lo-tNJV*#P42-tur2m*ub?TWi)G znvBZGH*n|--uWRBuYa$vcLWdzVk1Jre~-QbU!`?@_6sQfJb6tjkW?V4KvIFE0!amu b3IP8B)7_VjEAYZ800000NkvXXu0mjfiKg~Z diff --git a/files/opencs/scene-view-status-5.svg b/files/opencs/scene-view-status-5.svg new file mode 100644 index 0000000000..5a74f8e829 --- /dev/null +++ b/files/opencs/scene-view-status-5.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-6.png b/files/opencs/scene-view-status-6.png deleted file mode 100644 index 051aa64ae7bbd4d2e6bd51697eeb7be5063b1f0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1104 zcmV-W1h4yvP)e5m`!NZKorL(*(zG~=0Om@5c~pBu;`&4^x{FqgNRj% z=t)5lp?(}(!B(uOP!PYM7s0DmJrulp5Ja>^5JXFB!B&f(C}>^nZg%{=B$KAw)?LkR zDw04lGjAp{|Nnp9dy`$Ik}FXlQ6Q26b6US%48>pJ_nj~}Og%UUO#eie5(5WNeT%O!L7J0vc)b9aWSEa(8zgIK-{!u`eU5hu zVk11da&;2}r}6Q$WfKEVZaZLZj<+Ph*U{I`!8P4I8{F6s_tK3#VkhDfU||lX($XOW zrZOC|skD~3Aa>kqf{26;GB+LW@z9L_WOu~HPN04Y@XhWggqNXs2|||L$Rh*M^!T%s zuP4}nznv1GBEl&$RhnQArV(P0e2HNvgdYbTWv)afVDLa>A@w$ZuXJO>?~u<7;_4~Q zRS@w%UCmq3%d!zLP#|)_va1fcXNoae7Ku&{Nk>lmCWw?RW0jqNV1`6Hv9LJ^@PLSW znoDadB<>hdY#dH?3B#)bM^75`*< z$yh%JRrYO{%DwARI!n^>e)Wy=n2HkcF|WWc22r#)1pz*k0d%*@edtvh)vc<@^4=5b z%ilJw{$4oAC`N??%GD+wS~;!vy61!U+hTZXUvcNlfTuQXKfs?bXcxer4G$h!i;aLR z3kf-`>$#5saGK^H4RMvf0WR~&6)OR6N$)xL+R*&$!1Hcgrb^taRK>${NPWTx$l|k3 zKFR=m3g1daT}8%^byYfzjB>IK` diff --git a/files/opencs/scene-view-status-6.svg b/files/opencs/scene-view-status-6.svg new file mode 100644 index 0000000000..4321be4269 --- /dev/null +++ b/files/opencs/scene-view-status-6.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-7.png b/files/opencs/scene-view-status-7.png deleted file mode 100644 index 6b2e5fdc16cacc0ea7449461bd470b6eaf3b0b51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2462 zcmV;P31Rk$P)e5SZQoj*BL!;=FL81Y~$V7KtyeqorEJMlm(kL{o+EO)*x~Pjl6=^FV5?!K_ZYd--fd&+_B?ZFLIv6lG;N8Z?>v(L> z_Fm6@<2SYk>@l_E{PC0C-21+}o$uUpzkBZhlg}grlMIZDfvmlr-F#<{tCMjwHjJaK z-t>89-qk-QIvtk-WPT?kgHbZd)0uy2qQQ&tq_GT2aGzzpTXkDqKD|C|xdeV)4F$s{ ziCn0Pfpt{9&P1eRoq@j*%*#=LeyN5+5fdjul*J8SWF< zXwQVfP!C1Xq4u%Nu76BaI+htIiO2-_hYpNj8wmclTmztk!tB<|CNho802%!@2fjdH zWjX-bc9P}oI?IyL1T_>`$vPhn*6E?<)k@m(blXIx8g&B${+wwo-H{T<(5#J{!4mn0i@ z=QvE;mZvz-PZlq<`>=TNeB@5gfNV6Pt+N}IwN0p~xQQ8737K(L#t4G-22fAHo`*csXX5`q3*uMnLQcmZuC z+_;?D-av*@YW|H`6|Z4!nu7r=g!64aEL$=kd6bAk@-ANc1XrqRk*)fX9dAQBbza%! zgP$8=s#(HRzBYm-`I>~pm|;DLT=)?{MR`|H!imn{s}FI^N;kTF8vE&_ zSO**pR^W+##AF00l1873E4|A4W2GfWMV%oIc((Cd>;q?`8{hA{!2Qe*<~R*MUO~BZ z^!iY3nS#fb&4=A;!JYO_{Q1ZkJep`hhRcGF+xpPp^@H_c=b8|T9t&tOt`5AuU&W0+ z<<0gGww*gt(yn8QIG`J1r3;=YlBUXrvd^VC^RvnNhj+YKxO5>tla+=*Foe<%F2UH^ zf=ANgP|?wc+a4cgI}C6P02i!_CzcbjfQNe-G6T9{wDd7|@hWQdgget^RH)x-*@434 zi!|1o?sni0hfm^*)8i2&y=Bck$hH6(*6**8@nfvZjz<;a`L)hIJQ8Qrw5`jbx>v!$ zg8firE`$-D0Y^lWAMxGK2v|Qt)e+4jI-rA3u^Mr_)rT*;^I@}CH1Jmlerbe_C@pcL<3Un{Cvj}4;(MBJK#Ma5qf7C&tqRnY zZ)(|DMBCtVUk7yX31$PzLsq!gEQU#zaq{w21Z!(BC)tIvCJz!M?xc(|L&1wEa;aPC z`qRO8;<@Sll*qn6)n>yj+)J-E#L zJhI;#-86C|BlF91o#vPG60A7U(2b4jS8EIQBc5jj_&EfBsD|KqZP&m@^`xC^Xlo6s z!w!O+g-)8Cl+#|`E4B>ie0XaCoQ#J$8^df1`4Bh{mY4Z%Ch&^k4FkS7Nr^X__z>=z_x|GH^X887fC02c{WwHfvq z2}?!ApO^am{yn+VQn#pFk@*ep?yA-vAG9=QSO<&)_o1CtC)GBC-&BmbfkI8Ef^AOHXW07*qoM6N<$f_NmQ#{d8T diff --git a/files/opencs/scene-view-status-7.svg b/files/opencs/scene-view-status-7.svg new file mode 100644 index 0000000000..bcaefa0bba --- /dev/null +++ b/files/opencs/scene-view-status-7.svg @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-8.png b/files/opencs/scene-view-status-8.png deleted file mode 100644 index 65ea108ac54205654d7fff77793bd02a80e49274..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DWND0s)y z#W5s<_3bQ2zGed+7U?7Z>+guMU!A)6KqlYWu-V)kC#TtN-hSa;q1>T#;dy^$YR|Jq z&8Z2ma4VQ+`21V!0{*o^%lY3J+x{0`C^b9u@sy1#H?NYM@ocW$mA4}I_8wTTd$WPp zfvHb`rQ>uigK`UR4g=GXr)CX;huF**7#|f$AK*x2mTq7u+{v5JvZ0aJp<%}zHUp;{ z2iO!GV)B?J6mt@o1r+qQGxi9WZD8aOn9l$G$W7fm=J=0#g&%9h6+0hVe>iwx)%U|Q tdse4w?-&2FKP=_Mb0bryqQl=+`Cbc3rY~%JH6Q3t22WQ%mvv4FO#nbqZubBH diff --git a/files/opencs/scene-view-status-8.svg b/files/opencs/scene-view-status-8.svg new file mode 100644 index 0000000000..5f6107f774 --- /dev/null +++ b/files/opencs/scene-view-status-8.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-status-9.png b/files/opencs/scene-view-status-9.png deleted file mode 100644 index 72d0d9fb774b64180d1c73eba960683569ad160e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1990 zcmV;%2RZnOP)e5m|JWV*BQtEGdnvwyW?H2@3(rPQDZDSCZyO2K?Y+m zw0PGI5b+QtMR^FQs!;P#DQX+3Qmd$sec&aciQ1}Bo2qS;#5R}2DF(-F6B3fjV2Hsf zzJc+5z3aX2&h$HjXBn1tuw5In6(gN27IK*0UZ(t`}# z?+o0wk=;v}ExX+!%~!~C+#_ZVu6ZjAi9FZa-qtp}SQbv-eOc`{wii3VV(;6!+r7cW zaR+1*=PnRU6}c7xStbrLY6;;qarm5}X2uL0=fi$wf(d8C=EraL887 z1;e;{eH>p5T*Jud1oFv81!f*>YEj^EJs87SR6&B{kswH7_(6MX>ruu+_uO*JN(rdh z)y(A!{E-UTe0Wo#j4(y<)9DCmYu97lnomwwJQ$@X5t~oc;F~sLQjW zF-JtdFM*Ehel7YnGP8?G17@(=E0vlN&R+~ZrT||0L$0(Z`Z4~utyZFkJWS5y> z1Ot|AG(E8%HpzmqX)peGq8poYEhx2HaDFO;Yk?@Lvv}BOb{6HYVAa{Qcjh7nJ`z^3 zdH=K6yJrtXQCz--9wo2m34G^winAJA6!m|O1+d}q4S2Y`7_oQ)?f>`;ripQEEOwxG zCWNd02&yRB@;o?;B)je=!U%pjq`Wzuz~PUMw@$I&E_)F-xjpsd)zj`WyGbGc4hS+D zH$A3BeROgLzdv>c``0)Tr|@>(n1jm#lu9?Zs0c-IG@@dX79!J7NL8oY%F!BONVl$Z z;2)o8n`U>UbxYb#R?AYZoo>%hACkpiHI~Teo1BBaVjVVAtYw3un|$uOdKHhCI?+q( zV89q>|Sqyf@-Qfk{EBl}|?YoG+maM^V*v zysb|MVedcNv*RZ|Mfe48kwh0|VmPEiw~8Kd zwr+b8MR`t)O?mOBlik=?ZpR$0vi56psL$qMr5dig;(}jMFGtjp?&ED^sWAFycS{Q^ zheMNb99{@qzc(#w+Rk|a6!|W9N!FH9n;EUcK0No-4#-vsJy!zK7X=(L3kZ(vG3_v7u*MQ_(I8qlVc$q*xLlF#iGGqAowO% z4nCO(AR3LKF59G8Mvwd)?V7zo^~Wm-zmUjV^^~HnD{S(#e8?i7Bl74;NN{hhg;@}A z=JSh)4-KNaz>dyQKXN$QNvX;hD+!DKXFg^24{x_R7n*dr{W>3^CRy)zB761dQ8W{L zkyXI^6A^eC>L3UvbTRnhAyg6kJ43T55fl`rz%#Ir!eJny9$GHsQh#@wfQ}D7z~sM9 zp-eX81TD(%yBoAa*oU8ALX5%ZXW?Cfue1V=`HjgqA&dTh0tqiwoWB_RSF@cIK%U?9 z&g-wDGDkueomZaTy+vEF>^#HZs~P;D0X~I_?Hp_kOvKf{rhK)kACrricGxppie&VR z`%&}l8ss|c_;Tb1F85rdYLkmj61C1YA9({(as#3|-XB)}d~8nrq2cRQZ)yVG@902% zflUn3dEDp7#m{1-?dgT|1AjG|*DVTatjK4t_80PbVO`k=+Y`)tKXffYpfVQpU{q z3?$D&M%A=f=zj>>1D3J-YuR2X19Y~`jO(@xu=<}F*taN?J27>!<9x;mFj^?%;8`C2 Y3skl6XO~B$%K!iX07*qoM6N<$f>T$({r~^~ diff --git a/files/opencs/scene-view-status-9.svg b/files/opencs/scene-view-status-9.svg new file mode 100644 index 0000000000..5d101ad1f4 --- /dev/null +++ b/files/opencs/scene-view-status-9.svg @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-terrain.png b/files/opencs/scene-view-terrain.png deleted file mode 100644 index b0051eb1273c586e5ccfc4b3d371e7b8cb86127c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2580 zcmV+v3hVWWP)e5S9@$z=M_KSz1NNtJI>pA$2br2hCl*sD72+4Wp#EJ8MJ8_(c zWBcBn^U>=Drj$}rX_|bc_*ysL{mwbR^ZWhIXMpe(Mj(tp7=bVXVFbbmgc10kML@vJ zv|nX*?5oNg}`=47^3Q351AQ%kgNI^tNg2B;o zxN5b-yw7*+?RWqBEqwgg9eL~mh$_3ES+}1M7X${g^M1J0&;n};EOKI83_fC*_(Y(& zqesfkOo5!tR9+l)#FUBJf8UwiA7ts80zvojzVlsQ#Y$R$aH1%D7uUugtU z5TH0G11#w&JaM9xdWoV4<5Sac^j~#vo;vl>>-hYUsrASOsEB7IC%_%M3$z*$3N0Ca zNs=^*ECVHV4{?{2mz4%-$%$~S$p$64Sr8u+4Kl6~uqZ5AX|#1PpFaTQMY+&7GzRsp zj(1Tb-+q)rzHk9A@7;5FYh@XyiYiV@1QU`QZB9X_mB17q>u>4o(PSf?RI1QgD#|l6 z2M$+1tVlCKcmFWV`4*s}I1eJU8eS9$osx0%41(3(b?i?^j(_Ki6e8lH&%LVARlfVm zo)0n43Pf~1DpDsAiu}UlBvE78-H52F?nOM7L`<6H2l2S~qHd`(JYe}&$*#VU*Bu=_$p=Wj; z&fmNPm8{xojVT~ zTQc@<-?$b~nGjUbrXp}tjYfhtOz=jt9rChL;OX)b@Gbc76R`#@(>pr{?|*UyCcU%4 znmtvbK_3Z$z-RG!S2P-E>2N{!z$ip$1lYWO6^2tnoufR7ru28>>@^bn;lI59kMHB- zP)9-qsEFrer0}6*>*|99qn>k_5c|{){m%YjjXBi>nJI~UUERHo_>|fh9wj@XWBDp;5@dj-dUjfXJ?a5#|~k zWfi!WK^Fn;>6!cNm%a4$7vI{ssiMZ5mc(CDc}w=^nVCgV4IzDGWzH91D_5j}2{&In zq-fm?(&p^-CKRy|kzqnvL3T)-gd(jW(Kfa_!HAvc=oyreDngz)o&K#R%QY}QISsDC z5zuKQWo*jJ))wY~0gDeV9*hX+G}8Ixx$Ex;X{Ll4kJn4>CBE_v>65etw_9EIQ zri;+Rl*b!#3|dI74WJ#ic61|ZMu;^U5J@jg&&>MOy=WbMpP8FSo=?hJoDsNIpn@=| z&tl>l6ts-FEX>aNASEe*#YXFCY6n$N)tE>#=y3J>5V%*_AAk8{C04KV_YO~Jh^HmF zW_f(lBU|k*sjR>P$tWa}M68}{@q(DS968nYvX4p_xfC++7 zce5F2;E_0a68Mk`xY^?19l@ANFVd%Xc$}?4#HmXZlU#|!-ZMDL?Kj2{C1XrsXv~e& z%TS#CBaHp#C$A~yJdbA&iGy!L7nL#u;|qHH`eI7Yc26k*L=j(a~Mun@^c1VbgmZSAi z3`>=9n8|k>E~y|p6E>|Wf)woZmD@Ik?ZG*M>7xBqyevfx{OJb~%oJ34ZiU+he zOhFEM5drE5Ax+8Ao$Ro#zF96W%vNx43Ful(M*ha*`*eLOWFOJ1gJTm=gox`Sbx_yP2CMV3puWwi zbPc;hh(DO;k_D&>x9xc0XBg{^T~C!smLnX9xS#PRehBc+57E;lLPeEtb;)$~4T?@WE0;+Yb=%Nsd2cLf0rTr}Du@V(%YyYsuECpIr~}MguIiOnR5jN0i`^56(kDW|ER` zPUDDEab%-77Zk_9_>xpn<3D(Rr`f3qzb?mNr;kK82?Xaob3WY^o>ii8 z5EbWS@pBKYCvoDB7qn}4z-Z7*=9GjW79`To$_87P(mmpSC{@(B58l6Y0qRCoTT$%E zafe87m?xqiMP2dXWREc7Zv#5i4UFCsC)r{Dh#PgGfhK#8Vjmbgc=_BJZiniAk9vRX z0@Q%tczN${QOmDamgMpd5YGq4+zL)oM#m8nC)zVyvfAabshI<3KKb|Oic{V9VfSS# zKn=8S?@PbKf%d&8igR?xYkoAKpN&M7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene-view-water.png b/files/opencs/scene-view-water.png deleted file mode 100644 index 246c5aae929d28b3f7e8b86c0ab7b3a89da14bfd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 753 zcmVe5mc2_HK^Vs0*x5dg|4;!y$M9CDxE-i1fo zI}mAtNQQrjohWGD0nH#t=R5KOVYLuc=J!ku5J{?mm^}sU+EFz)Ofb1$QSlfcTeXIa z3mpSQdH`}4<#!N1?4OOWL75h5*V)D25r8;R|Ezv91Be6xNTi>f>M%>{r30fO(=rYQ`o12j$oWV>mngq3*$ zEMOOAFYH20SO~(PMdVe$9L1Uw6>|gN)i2wHI&}!G;MT&q(VBN;ANd0^t~E$B%mJ_b z>w$f^JNaYj;rUgE4i<8r=~b&;@E6BBM7!a8glH6XqZl{E2j8of$vr6%IaM#~G{Sj4PF)2EmJFVQtJOPp>GK{! z#^Ufw>Uew(F8? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/scene.png b/files/opencs/scene.png deleted file mode 100644 index a9fc03f7699a715592edd31db78ad47ac514f006..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 401 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=d3- zBeIx*fm;ZK886+f`vVk|Dshb{3C>R|DNig)We7;j%q!9Ja}7}_GuAWJGc|_p9 zb;Z-gF~q_@IYEL|Lh-@t*V$Z0!W(|@3;h57`SWl7MU6jL1fn1C7%foY7O>~x;9$^< zV?46nan?dM8J17ifB*j7{6vn)P~L3ze@4}JjVcW#`~vyxj{+3ZSROGu*l=r^OTSW5 z+@a{usPC|cS&bod0`rpcLq{C+94D=3J@wbg?14$Z0;WjA3G&=u&!s9@b3Lg^NoPKq zx442?}xUyxj@&tLlBR3uPh;*bY?0g_T;e2NvgD_{~i9UU^lm<3m=10dDY-l)k mC|qH;aK}@(V7oL928Qm_H3#l%PWuYpUXO@geCwN2ZUMx diff --git a/files/opencs/scene.svg b/files/opencs/scene.svg new file mode 100644 index 0000000000..a516d6b596 --- /dev/null +++ b/files/opencs/scene.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/script.png b/files/opencs/script.png deleted file mode 100644 index 29d622e19f742602321a65b981b63b35a845d401..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 256 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;ir{ zi(`m|;M&QFe9a0xtw&dGZ{E$cJ(#^Zb@G;FhmxN7OJAz1%{|Ctac-y2H3{cs)kS&_ zncu8tk#ERT1(t)Rzy*9WmOoDA;y$eY3YfYl_PA)s{owN+d}Ufd6E zODZw+YwuWfDs%xd`aABT4!&+SdkJ+6JHv3vv zuiCSAGn15p?Xv3V1u2W}-(vc^?i%}}O@^y~eYJ1@R5GndeerRi>lr*<{an^LB{Ts5 D&n9F^ diff --git a/files/opencs/script.svg b/files/opencs/script.svg new file mode 100644 index 0000000000..76f3b7b28c --- /dev/null +++ b/files/opencs/script.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/selection-mode-cube-corner.png b/files/opencs/selection-mode-cube-corner.png deleted file mode 100644 index b69ef1782fef094243f5e5f68b96218b2699bcd5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1422 zcmV;91#$X`P)Z~{#<2u7WO5QIbr28@YH;v<5TiAK0TNO{Ep#7Be#Qfs8hpsg*BVoT|Zw)dRv zptniE0)3nd7`w)2@3Yp~d;fc_|G*#yF^EC*6(W)&B8&Z)%iIC7*}yX3S>Q1s6DSV^ z0^?lQeN|OIxn~4K$u0%P0`rV9+1A=}padvZ)nB5|8NdaafT^l_=8g*xkx{@%UDvZsy6k9N3fS<dDnc;A_+^=n?s(VCarihfP>LYy}@ATrC;Xo^} zMpZvl)pEZNx~i@QE{MqHSO{=|kE35dW>D2xK#_>N=C^=2Rows_5s~j>Ty^t+4@4x_ zPXS$DXcbT(B5Px~3Jt*H#+V8b8RDye=nF;wTSY`-D5A_-+f|wT6%a+XF(%xXW$sU9 z0*;BueqZi^8z!$2kww6tKzu(rTEOd8s;2<6ftMY}ndo_59q=`vR7m>pvvRPHv^})!U19o|ycTH83 z2fE#Y1Kg{#M5IH#L!pqh)~*MNfrG%i z;c&Rh@2wjX0RYxofcsU|yVK#w@o}z%yOHi3{^Ph?0y^%gGtk=v=d{;kVOdIB^&H|e z5s^4m?YLJ2xZM++X}^?@lduBs+5&=UW8Ks|8b+<6IW3pQAz381TqgJEic`RZdqF^( zi8Q=bIH#b{2GggxDS0&IuB0t7oh#`N(Ude4*935^hhsYnRa0UrsZ-XCFEm^B>^*p5 z(H#>Y3+HAVw|R+)G)%Wn(({oKGg3|6NouEV+EPC(?>e%^S_~FrEkPT(Zk~YWa%Rjt zCbrZThD_qxaLMs)u@cY`n)_ri1fKGe^Kv`V^J&PQ&XtUZX-Z6^H7=3q-z}zQLq8!zkme{7RU22*v`1-M!>qa z?#~zp<2YE)1M5{|?QX35qs2Y>S5ZpkGjLy1DAX(>52$LTuL5*mi4@MhSdo=ADYrIb zG{)ZMvW=1jux_LZD8qV(tn2NsDoW`wR~JKzSUU|kDWr-m$KC6ezDMO6iNI`c*{U1ImEWzFuUi`x-E&VBF!A#JTOa`1pZL zAW&75(kJrWL~H<#ipT-~L;b!3zvHzn8h>mPp6Pj*^;}FK_|d5CjZX}~-L3>Ch{&5j zyRTLvy#MgtK;4OL`K84~oSt`|bt7P$pGR$POdf!UTwr}~`1Sc72$vjxqtI*0doW|{ z!it=!H}ls-8)4b~h?~oR8PRTF_3g&=yE|Pt`_YQZ=XT`QWR7Lpo<)={*p2H1uB%2^ z??hEm%G6tsU3!xQSKK|p#l%W-+cP3%tdTu)e6AFfZ-oEbX&*Hy`WJ*pna&~pM zyIS$RNpSwJK&P#kH=elEGTQ4o75Wy}2@vOYFr>AfVHZx5RCk7i%O|lNjrEv7F>n?* z;Aew)z<+2&Bov^1@3pKMBwYFl+jhRnShvguGY;86(*9Uk diff --git a/files/opencs/selection-mode-cube-corner.svg b/files/opencs/selection-mode-cube-corner.svg new file mode 100644 index 0000000000..63d5f73c56 --- /dev/null +++ b/files/opencs/selection-mode-cube-corner.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/selection-mode-cube-sphere.png b/files/opencs/selection-mode-cube-sphere.png deleted file mode 100644 index cb5432eea6f3e668540da5ef909db8937546f3ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1428 zcmV;F1#9|=P)->{SrNnt;ZL57-nC3mRb+C|GFON@;gz zt`D7JHEn6Pv(<;5r<<8ObN~Ny&pG#=KlEC!^;)lW)kI{dh|KllU#bVd<^hiZlYqN` zT%gLf?b{s3c~Wcr?*E2>2X;PC2+UAQcpHIfCk_xt@T@4>ngt!U2DAxI4dGAr-Oh4yyJO4ZKk!J3WP;uh2I1+wAL>I`$S|{ znyc>3zy=Y?_me=2hn524Mday@=7l2h;!q@hFchgbp-9|>-mf>__+Tg!e;6P$rcHZE z4UAJtofMINz7p^j7zDf_BGLx%iJ?gRg0k!bKoNKtjlmliDJ!r&6p7ayTlnh)m8^B! zS>uQ?rlm6ZOTe={rBt-_Hishd*OZ;HAGqGQPP-o(HwiG{VsJ?L`SCCP*->Qz4v5HS zzMKR9`uS-QnFE{vGDDI0W@QH!8Yj{D#B-CJEVwr~cvIt!>LpooMC1#KtkJIP zUI5B~rM?sJNbD4O=)GBwEID~7kd<#I8{7A~Qn*+crLc@kPGg*b=Z=tFQ$=?5x3if& z?+@H7XTjP6%yV70N^9M62CYa7fo2k)uAZ{&?d8Kx?Bkc=lL)v;45}HhvRZ*XN_NdL zvT6=vH=H5Se6m=N+>Hfw6nnke?OgLT(|s z)kn#^e4d=s-w>#+pegqrE)OZ9dc-v9#+H(_nwPU(2jwMs7G_u|62C7N4({(!0>E7>=2F86dgMqv5EgWtyv_ZJeu>v=cVH$3qBkTMsSQ#aOA`&Xr~NOBIBbGJK`_`gMQk=?f-j|BGq7X&sb%kI9aU~R9Ia4_l^Q{V0QcajaUaBxqLRUuD# zD8+3oZm8JWuX5buse`vOxb@JM-|n3Gz}o1p^4HU~0|^n?E+W+;;&@HGiEMm{RB{CsFq0#&DsrbDnGKitg7Almm@g>%ncktXUjbqoz+-hEjbx4?Y9qBwmSy zgU@@9hAE}$MC2x|^(kL>Kx;hwF+0yS;JJ&!)ahy=ph)l_!8vBJvE-*-Goz|YsLXx&(>23VxE)*@nnY^`-e7Z1ew zz%9Vc_N4f9_5;I#a~_aufo+lE7x1^b-6F;B0oFyM(O61%sqR)Lpi(KNqS2`Go@D~J z1B-$Cl~Q9oDZUIi4(#?*AXYxaZHaB$I~~XQ(XySL zrl4^F{@?~oGzcVWqQO8A7A7j_Lfs02nrNi+wNfbR0-_O1QY0u;Bmo3k3T+1pZD;1* z@E|Y*Xb1!X zV_esLSyex|V*pZS7Xzb#xyG0xYi$Ei18h^(UsBH*zy;cYDXMzpmIH{$2w*s{1gJ=v zZ3Q+1dx2f5df_JThJnk#AY)9MwRWJYw)e(Iu$yKxfpNf#z(Qk8v9&e^Yyo}*-i=11 zCwlsERgIBcidfev7W zs(z@d4SrvARb37=ipctO0Jy-%slT5#sp>4?B@ubWZ-5L{T?6bEk?+zRbxVQwMWn<} zfUAFK88BHyR;F#iS}fhdlQ*} zJt9)=OCGpk@zWx*05}R{_R^yTylX=BMBqu_dB<_ad7jq-d=4y2#}1&XmB6PWa z#&XAT91*!BB8|Y-6x7q6=NVNkP}LPchVMIY4aBP=QY#|sRdsa_3c0_4t)Ay~s%my$ zk6W;hXLXi{G>b?R@HOxpFbbG=Lm{{JY9W^b3ju5WxGzB;F5J*aBx0?#Zv)$aoxnTM zX!NAtM>j110M=T7yHwS?)yVf<31b{A+ zjJ;Mddvb*h4t~fTSW0`zQo6DR(>C}X+QWlzO#sJwIQB}QYR}3gcjD@@3bSE*<<7bV zw+uka=M)+D(jt?LO|?$=iR7^9xu#`5%~RHGXe}tc&aAN(gT+`&&?c{2C*Zjp88w%2 zmzyghCTnH1X78qS0VE=G9^M9lX!6K)c$+w1v^ zv6ep`d6RKFUQj8Yf45dupYSz|YE_No9(wOY;rXKktVh6e|26NI;=vHdrhkM@Tm{5^0|1Ct z*H#8v>Nbr%xQ(Qfo+R#AkNQX7!p$lD0IcIj7`+~%X78#Buf23+{^;`K#Z&rLhGVeC z;28(UdSrK9q@ejQ1KN&ZLxbS*-$&Dui7R*2O9d;BcRs$ktS@jff3i-vE?TwoWUnGvpDN7qxn*SJ9wOdKedHTlCqRaqAg801 zg2n@cTaFNF+Joslc?$T_1`Cecz<`~84v704)I}r`AYR#7IGxbhpRrwMP8#bTvcdda xHW02($28klfE2_tzyY8^Rq0p1`qi&)>mM%IDJw43e1iZ0002ovPDHLkV1oE-h|mB4 diff --git a/files/opencs/selection-mode-cube.svg b/files/opencs/selection-mode-cube.svg new file mode 100644 index 0000000000..e03138bafe --- /dev/null +++ b/files/opencs/selection-mode-cube.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/selection-mode-sphere.svg b/files/opencs/selection-mode-sphere.svg new file mode 100644 index 0000000000..9f8ed79340 --- /dev/null +++ b/files/opencs/selection-mode-sphere.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/skill.png b/files/opencs/skill.png deleted file mode 100644 index 0ef7cb1cb7eb771b6ffc154a04239e908d7902d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 318 zcmV-E0m1%>P){+JVsf%67$h%QQlms+#U}%j z#7H7U3cxh#Ad=t%R_J4es#gSu1{@atS6qNBL>0i-0pr{Ey*(b1U#Q5DH5HG&MVx^} z3P#YJuE3M#$E2A_igwhONPx|Ypbe9Z?~7=8`cu7t6hN-EzRu@)JY!V-0AP@yMw+z3 Q1poj507*qoM6N<$g3nBYc>n+a diff --git a/files/opencs/skill.svg b/files/opencs/skill.svg new file mode 100644 index 0000000000..21ae4d12c8 --- /dev/null +++ b/files/opencs/skill.svg @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/sound-generator.png b/files/opencs/sound-generator.png deleted file mode 100644 index 79833df9c36be3f7693f32e60ae3bf39bbd9ff21..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 429 zcmV;e0aE^nP)H1=6R;S0V^yyQTqL8bXP7v>qmG4(T=OJSw8W(~k3Bxd5 z38d+2E^Vw;>L+4}xFXJLXHY#X$kxF870sdu>JMtCX3*;R1Mn{cwz$q=zR8z*p87L2 z&_OD+1&E^=KaSz{Gk%YJ?c(CFpW-Gk7!xkoFcYQ;tVb~d*E`USUC_IhlFbj|2~m3o zRSn#YLRjY+@Ru#P-Kt4$0JcHBf<{|tzry5 Xbk3C9O!f{e00000NkvXXu0mjfLg2Qp diff --git a/files/opencs/sound-generator.svg b/files/opencs/sound-generator.svg new file mode 100644 index 0000000000..4e27f19f41 --- /dev/null +++ b/files/opencs/sound-generator.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/sound.png b/files/opencs/sound.png deleted file mode 100644 index 86871611f5deae184cf6b986797020563c5c3ce7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP;i2$ zi(`m|;M7Thd<_ab&YYdcn@< + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/spell.png b/files/opencs/spell.png deleted file mode 100644 index 5890ea751b40483621bb334e57cd1bfabb8c155b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 319 zcmV-F0l@x=P)3$eLy(<*LW8GyUB_QiON@i7d;v&Ld(k^d!u2Gx@6y_deauH(Q;0>#iS z21qdF)xD~^oT%n66@X|Um%DN8`(B<3E!@aL?DWCL&uR;-l=q_S27NdeaHRKnBH6}; zbHf33(<&k^73jdwDDmq$D+X;b{lBa-o@t~WeJ*002ovPDHLkV1f|fiGTnA diff --git a/files/opencs/spell.svg b/files/opencs/spell.svg new file mode 100644 index 0000000000..e726d85a7e --- /dev/null +++ b/files/opencs/spell.svg @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/start-script.png b/files/opencs/start-script.png deleted file mode 100644 index 73ed157b9ed2c2366388723f8e5b25fff4bc3b97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 359 zcmV-t0hs=YP)Q%iD#FbuV^VUy_zGW14pjAq*dWSMD~X{RfgUV_h! zasz4tfSa9O0ryxh=a?XB zf=o_JY^ti7sR{sWwoTKF2s#(|144HJfK!Jo%YqE1X-W|M{1Zd~{5ylcrur;`3~-I8 zuIr0ees$m;`MIkGG46U-GxZ99s|LS>;vs3-Lt1pt{7g}-u;fDPya%O?-LK1jXrETj zY5{PM3os7K82hm0O4G9UrzV_I}Un^?hcdO-#kvH8dF z#R=Wh@(Uh6k0j)d-_HyP1Xtj^h`ff5tb+0Uij%io@C{l + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/static.png b/files/opencs/static.png deleted file mode 100644 index 9f458e5d04a64ca5f077f3718b92f0fabbbbc857..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP_W6< z#WBP}@a-f+u?7Vm=jeCr-|cx{$S>NnV)Blgt<$GWVL5T_R>w*s<6{<@22bxanl89j zwW_Q!xy_<|+l7kYlOk`X1TFe5x9Lrw1&d$x5>D3%+}k6 z&#_5$VsgdvYi>W{A66Z%dwhQ%)8(Hsf8Ot&`Y$-2`QiK?t)5lqGJ&pP@O1TaS?83{ F1OVZBQ%e8< diff --git a/files/opencs/static.svg b/files/opencs/static.svg new file mode 100644 index 0000000000..8e8ce541ce --- /dev/null +++ b/files/opencs/static.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/stop-openmw.png b/files/opencs/stop-openmw.png deleted file mode 100644 index d8f80967244e7a3fcb143d5617a0287666c7ca81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCijSl0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=d3- zBeIx*fm;ZK886+f`vVk|Dshb{3C>R|DNig)We7;j%q!9Ja}7}_GuAWJGc|_p9 zW$)?Y7-Hd{{OA9FduAR3Bcnq%TO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/opencs/transform-move.png b/files/opencs/transform-move.png deleted file mode 100644 index 1e5bd573d3bc6f57af8f81aea55e2d0ac8d18c78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 879 zcmV-#1CacQP)e5n9XVwK@i8MXIBgo4N5K|*(VT0f+7g!0R;5{Ja{mA z@dNY{@BzH*NhA0SUc4$ILayQq=!yqXK_dnsyX~*$qqmZs&Q6@}6?F!Nt*NR0*S|id zyOJbnuQmg12HFh#vl&=;us$bu%mCAv;9x)Dei_q8v;fHbMbqgnnY4Qp`C6Y~~AnLIB#dksQ2* z92;Xs*h&mZUVM$5IM08V&<60Q>-GOtfL&yUdt%`74V$!+-*OVyxWL_8!|eA>aB!#h zXhWS)ek^S3u~rbh)eXzK8%i}%tnCEg=VWxr+M(OiC!+7t03VhUJF&yf*DPIR%iWv= zF);u@FVP2&-N~8{X3Pc^eIfh(aL~c=P{?4D`}gTJJ2|z*js(d%X+ci(n_%73X4_?N z01%Kqf5u3BhU0(xy6R_ozQ4RL!(4dy-MUf2r>{nF+M*h7P@E)?4fjF`JAI6Yu$$F0 znOZj;F;DMToE`8v?D}31@h9FbQi&Dnc{dNFC^IjrI!H=iUfCp3KKk^fF~2q&{J_sz ziG?VrytL4LWi}?3F2-JH1wqX7{FIWO0JYXf%o>Ap7;U|Lda>f&03RtIvHTf&f%Tua zu5S2*gO%Ruk1}E#Kq_C#d=u#kx(l%t_Be>% zauAIMGa)V~u^hD~HB4wn*eXASiYI6bLF^smFEao_SY}0LZkAz{}D~GShS2LiaHk>uCYe zO0T%1{8ze+AxlsB5aIVcm_c{C2pn6;HUn)2+6*+vz%SBJ5jawBdHVnW002ovPDHLk FV1fq_mMQ=M diff --git a/files/opencs/transform-move.svg b/files/opencs/transform-move.svg new file mode 100644 index 0000000000..1b9490f001 --- /dev/null +++ b/files/opencs/transform-move.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/transform-rotate.png b/files/opencs/transform-rotate.png deleted file mode 100644 index b6c6bc58a78f2da3deedea488870a5c481cdb758..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1391 zcmV-#1(5oQP)e5m`jKpMHt7cW+p4XgBVakA|Zr5D2dT1DyY$eN)AR8 z6x3)`FbYO67d0y4E9(Pq@sI@rV!(qaXb=Q1iifzMlA!olA31MuX^<;)s{8_Z3Nl~v=R6}M?eqb{IXqttU*vo6t#F?^T&pvT7|IV~{7$8<_+l_lhfagmK&zp3O6zsh zUqgk0@jG;P(H!*zG*%cKqBE2Oq6a6oDic>nQIiZ~CTpU6hl%FMSLYGk=^n$07l*Jr z7wP4#1`U*o<6mJ!4<`mBF-(!6r^*A!EhhR)tpg&bt@_#Wye9p|8Ni4OQE;3kGG1l8 zD9898i2k7pLvA*4l(5MX37lfyL^coX6S8(_-N*qHoC5Ht5Fs{|Iwbm(?UjQ{U8*~& z%0&M9Y#!LRBl}OPBV&8(Mh<}XU2@ozaAh+m8ydgtl6t!|!*+@GU1Vn^o&C_m^na59 zjJ}Oi?R67d*>8mxTIA>pDkymwBe&Hon_B?6M0L6ksW{q&VfO`rG6~$ri2Sqp(vl3u z(bHf&8G;ey07_)@&Dza3hzy5p~j0qXa?lyaGVt} zvkyg=3CO60Ond~!o8awiisiir0Kbv6cH9g|#8^)EH&s?QAH%OKkD*tN>m!WKl*in@ z*8wJPa*Y>ZjQ<|s11`|0Z48!Y#e>kjY=l*IFGO>@>EH8T*dY#_QvgVw39l7+3)9(5 zW?$l%B|he7If*NaMSM7nxnEl}~1$!kY2PWUINOcJ+Mda;n;12U4NEe1SRghD`U) zCvqDc=1@&AMTLcgZ-&ZfQ<+Q4ZuF7s=Qm&mM#f=i&^7hrOSX x`4dZw{sg1nqHaqYfi?nd1lkC+5g0iF{{kJs-=Z!HNr(Ud002ovPDHLkV1lUTiDdu) diff --git a/files/opencs/transform-rotate.svg b/files/opencs/transform-rotate.svg new file mode 100644 index 0000000000..29bc3fdad5 --- /dev/null +++ b/files/opencs/transform-rotate.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + diff --git a/files/opencs/transform-scale.png b/files/opencs/transform-scale.png deleted file mode 100644 index c641259bd731fe3203d2404fa98a4203793e2d00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 436 zcmV;l0ZaagP)e5SX&Z;Fbr(bUyk7Q^h{n)kKi}d1f>&ZfG)3sI&E4M{@O1hSw2yaHDd6n$M4k88P^9I{+1@$z9x{Ur(%KoHO( z=VqWg7?rIF3P6!&1K)`R`+yXPrx18DV6NA(f)r?u(66k>$*>MIY!*m?b`xj63xF7) z0J3gk0JJrMmJm0wrEKLoqyRVZ%BbSw>-_}m%j06+kCGaNe0LO e86X4yz`z^kUV6rk&j!T+0000 + + + + + + + + + + + + + + + + diff --git a/files/opencs/weapon.png b/files/opencs/weapon.png deleted file mode 100644 index e2c1d3dc164fe4da0dc6bb9ea6ae6bb6cc6d2411..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 394 zcmV;50d@X~P)adGKj;`d+DAT@201E=Rt?)kr*leX6P{U2k@J$V@f z!JJwsJh}(ukQ@VR7jn}qz!iDJ(~qcgAQQ5L&J|W&82Xe~ARY8E`hl$GS;+xt7UYA{ zz%)gd(POff1z*K5~FCU6u!Fw7_|An$o5`3DfF2qalS#zpY- zUGxyp%5^w9Q-A6N3($&5>YP7$p4SY+aAPe%pe0DfTKlQ{Q55MLd(H&>ft;cr0UTn! z#@y3c>O0WEQtV-z#AY3JnQTWQFF^knjeP+`QZWOvnF*ljoEO%LcfO+@VE%RpW2Ol% ozL8phl-XIoP8VEnA>PQ_Uo!rSNh?8@vH$=807*qoM6N<$g2$kv7XSbN diff --git a/files/opencs/weapon.svg b/files/opencs/weapon.svg new file mode 100644 index 0000000000..6168812305 --- /dev/null +++ b/files/opencs/weapon.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c2f63259d8147db4927eee133812ef30a049a410 Mon Sep 17 00:00:00 2001 From: Alexander Olofsson Date: Sun, 14 Apr 2024 10:47:05 +0200 Subject: [PATCH 390/451] Add missing developer tag to appdata --- files/openmw.appdata.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/files/openmw.appdata.xml b/files/openmw.appdata.xml index 764082df00..d20d0740a8 100644 --- a/files/openmw.appdata.xml +++ b/files/openmw.appdata.xml @@ -21,6 +21,9 @@ Copyright 2020 Bret Curtis

org.openmw.launcher.desktop + + OpenMW Contributors + #dcccb8 From e0a016749c7c153d0bf123e3db9ee39d6f40a3d3 Mon Sep 17 00:00:00 2001 From: Martin Otto Date: Mon, 15 Apr 2024 06:47:48 +0000 Subject: [PATCH 391/451] Update/add German translation files --- files/data-mw/l10n/Calendar/de.yaml | 31 +++- files/data/l10n/Calendar/de.yaml | 49 ++++++ files/data/l10n/Interface/de.yaml | 54 +++--- files/data/l10n/OMWCamera/de.yaml | 74 ++++---- files/data/l10n/OMWControls/de.yaml | 86 ++++++++++ files/data/l10n/OMWEngine/de.yaml | 257 ++++++++++++++-------------- files/data/l10n/OMWShaders/de.yaml | 67 ++++++-- 7 files changed, 408 insertions(+), 210 deletions(-) create mode 100644 files/data/l10n/Calendar/de.yaml create mode 100644 files/data/l10n/OMWControls/de.yaml diff --git a/files/data-mw/l10n/Calendar/de.yaml b/files/data-mw/l10n/Calendar/de.yaml index b3ee621402..3c6b672dd2 100644 --- a/files/data-mw/l10n/Calendar/de.yaml +++ b/files/data-mw/l10n/Calendar/de.yaml @@ -1,4 +1,5 @@ -# source: https://en.uesp.net/wiki/Lore:Calendar +# Source for month and weekday names: German Morrowind GOTY version +# For reference: https://www.elderscrollsportal.de/almanach/Tamriel-Almanach:%C3%9Cbersetzungskompendium month1: "Morgenstern" month2: "Morgenröte" @@ -13,8 +14,26 @@ month10: "Eisherbst" month11: "Abenddämmerung" month12: "Abendstern" -# The variant of month names in the context "day X of month Y". -# In English it is the same, but some languages require a different form. +# In German, there are two different options to generate the genitive form of a month name: +# +# (1) Apply standard rules for genitive (too complicated to elaborate here, but you usually add an "s"/"es" at the end of the word). +# (2) Use the nominative version. +# +# Nowadays, option (2) is more commonly used, so let's apply that here as well. +# Let me add the names for option (1) in case we want to switch in the future: +# +# monthInGenitive1: "des Morgensterns" +# monthInGenitive2: "der Morgenröte" +# monthInGenitive3: "der Erstsaat" +# monthInGenitive4: "der Regenhand" +# monthInGenitive5: "der Zweitsaat" +# monthInGenitive6: "des Mittjahres" +# monthInGenitive7: "der Sonnenhöhe" +# monthInGenitive8: "der Herbstsaat" +# monthInGenitive9: "des Herdfeuers" +# monthInGenitive10: "des Eisherbstes" +# monthInGenitive11: "der Abenddämmerung" +# monthInGenitive12: "des Abendsterns" monthInGenitive1: "Morgenstern" monthInGenitive2: "Morgenröte" monthInGenitive3: "Erstsaat" @@ -28,7 +47,9 @@ monthInGenitive10: "Eisherbst" monthInGenitive11: "Abenddämmerung" monthInGenitive12: "Abendstern" -dateFormat: "tag {day} im {monthInGenitive} {year, number, :: group-off}" +# Standard German date format: d. MMMM YYYY +# Modified example for TES lore: "16. Herbstsaat 3E 427" +dateFormat: "{day}. {month} {year, number, :: group-off}" weekday1: "Sundas" weekday2: "Morndas" @@ -36,4 +57,4 @@ weekday3: "Tirdas" weekday4: "Middas" weekday5: "Turdas" weekday6: "Fredas" -weekday7: "Loredas" +weekday7: "Loredas" \ No newline at end of file diff --git a/files/data/l10n/Calendar/de.yaml b/files/data/l10n/Calendar/de.yaml new file mode 100644 index 0000000000..c02765c5c2 --- /dev/null +++ b/files/data/l10n/Calendar/de.yaml @@ -0,0 +1,49 @@ +month1: "Januar" +month2: "Februar" +month3: "März" +month4: "April" +month5: "Mai" +month6: "Juni" +month7: "Juli" +month8: "August" +month9: "September" +month10: "Oktober" +month11: "November" +month12: "Dezember" + +# In German, there are two different options to generate the genitive form of a month name: +# +# (1) Apply standard rules for genitive (too complicated to elaborate here, but you usually add an "s"/"es" at the end of the word). +# (2) Use the nominative version. +# +# Nowadays, option (2) is more commonly used, so let's apply that here as well. +monthInGenitive1: "Januar" +monthInGenitive2: "Februar" +monthInGenitive3: "März" +monthInGenitive4: "April" +monthInGenitive5: "Mai" +monthInGenitive6: "Juni" +monthInGenitive7: "Juli" +monthInGenitive8: "August" +monthInGenitive9: "September" +monthInGenitive10: "Oktober" +monthInGenitive11: "November" +monthInGenitive12: "Dezember" + +# Standard German date format: d. MMMM YYYY +# Example: "23. Februar 1337" +dateFormat: "{day}. {month} {year, number, :: group-off}" + +weekday1: "Sonntag" +weekday2: "Montag" +weekday3: "Dienstag" +weekday4: "Mittwoch" +weekday5: "Donnerstag" +weekday6: "Freitag" +weekday7: "Samstag" + +# In German, there are usually no "a.m."/"p.m." shenanigans going on. +# In case of ambiguity, "vormittags" ("mornings") and "nachmittags" ("in the afternoon") are used. +am: "vormittags" +pm: "nachmittags" +day: "Tag" diff --git a/files/data/l10n/Interface/de.yaml b/files/data/l10n/Interface/de.yaml index ac1a95a0ea..8457797422 100644 --- a/files/data/l10n/Interface/de.yaml +++ b/files/data/l10n/Interface/de.yaml @@ -1,28 +1,34 @@ +DurationDay: "{days} d " +DurationHour: "{hours} h " +DurationMinute: "{minutes} min " +# There is no abbreviation for "Monat" ("month") in German, so full terms are used instead. +DurationMonth: |- + {months, plural, + one{{months} Monat } + other{{months} Monate } + } +DurationSecond: "{seconds} s " +# In German, "J."/"Jr." exist as abbreviations for "Jahr" ("year") but are seldomly used. +# A plural version of these does not exist (at least, to my knowledge). +# +# To avoid confusion, use full terms instead. +DurationYear: |- + {years, plural, + one{{years} Jahr } + other{{years} Jahre } + } No: "Nein" NotAvailableShort: "N/A" Reset: "Zurücksetzen" Yes: "Ja" - -# To be translated: - -#DurationDay: "{days} d " -#DurationHour: "{hours} h " -#DurationMinute: "{minutes} min " -#DurationMonth: |- -# {months, plural, -# one{{months} mo } -# other{{months} mos } -# } -#DurationSecond: "{seconds} s " -#DurationYear: |- -# {years, plural, -# one{{years} yr } -# other{{years} yrs } -# } -#Cancel: "Cancel" -#Close: "Close" -#None: "None" -#OK: "OK" -#Off: "Off" -#On: "On" -#Copy: "Copy" +Cancel: "Abbrechen" +Close: "Schließen" +# This one is a bit tricky since it can be translated to +# "keiner"/"keine"/"keines", "nichts", or "leer" ("empty") depending on context. +# +# Using the "keine" option for now. +None: "Keine" +OK: "OK" +Off: "Aus" +On: "Ein" +Copy: "Kopieren" \ No newline at end of file diff --git a/files/data/l10n/OMWCamera/de.yaml b/files/data/l10n/OMWCamera/de.yaml index 62b50c5862..76ff7bece3 100644 --- a/files/data/l10n/OMWCamera/de.yaml +++ b/files/data/l10n/OMWCamera/de.yaml @@ -1,72 +1,70 @@ -Camera: "OpenMW Kamera" -settingsPageDescription: "OpenMW Kameraeinstellungen" +Camera: "OpenMW: Kamera" +settingsPageDescription: "OpenMW-Kameraeinstellungen" -thirdPersonSettings: "Dritte-Person-Modus" +thirdPersonSettings: "Verfolgerperspektive" -viewOverShoulder: "Blick über die Schulter" +viewOverShoulder: "Über-die-Schulter-Ansicht" viewOverShoulderDescription: | - Steuert den Dritte-Person-Ansichtsmodus. - Nein: Die Ansicht ist auf den Kopf vom Spielercharakter zentriert. Fadenkreuz ist ausgeblendet. - Ja: Während die Kamera mit ungezogener Waffe hinter der Schulter des Spielercharakter positioniert ist, ist das Fadenkreuz immer sichtbar. + Beeinflusst die Verfolgerperspektive. + Nein: Die Ansicht ist auf den Kopf des Spielercharakters zentriert; das Fadenkreuz ist ausgeblendet. + Ja: Die Kamera ist bei weggesteckter Waffe hinter der Schulter des Spielercharakters positioniert; das Fadenkreuz ist immer sichtbar. -shoulderOffsetX: "Schulteransicht horizontaler Versatz" +shoulderOffsetX: "Schulteransicht: horizontaler Offset" shoulderOffsetXDescription: > - Horizontaler Versatz der Über-die-Schulter-Ansicht. - Verwenden Sie für die linke Schulter einen negativen Wert. + Horizontaler Offset der „Über-die-Schulter-Ansicht“. + Größere Werte verschieben die Ansicht nach rechts; negative Werte verschieben die Ansicht über die linke Schulter. -shoulderOffsetY: "Schulteransicht vertikaler Versatz" +shoulderOffsetY: "Schulteransicht: vertikaler Offset" shoulderOffsetYDescription: > - Vertikaler Versatz der Über-die-Schulter-Ansicht. + Vertikaler Offset der „Über-die-Schulter-Ansicht“. -autoSwitchShoulder: "Automatischer Schulteransicht wechsel" +autoSwitchShoulder: "Schulter automatisch wechseln" autoSwitchShoulderDescription: > - Bei Hindernissen welche die Kamera in die Nähe des Spielercharakter bringen würde, - hat diese Einstellung den Effekt dass die Kamera automatisch auf Schulteransicht wechselt. + Falls aktiviert, wechselt die Ansicht temporär auf die andere Schulter, solange ein Hindernis die Kamera zu nah an den Spielercharakter bewegen würde. -zoomOutWhenMoveCoef: "Kamera-Zoom bei Bewegung" +zoomOutWhenMoveCoef: "Kamera-Zoomfaktor bei Bewegung" zoomOutWhenMoveCoefDescription: > - Bewegt die Kamera vom Spielercharakter weg (positiver Wert) oder auf sie zu (negativer Wert), während sich der Charakter bewegt. - Funktioniert nur, wenn "Blick über die Schulter" aktiviert ist. Der Wert 0 deaktiviert den Zoom (Standard: 20.0). + Bewegt die Kamera vom Spielercharakter weg (positiver Wert) oder auf ihn zu (negativer Wert), solange sich dieser bewegt. Der Wert 0 deaktiviert den Zoom (Standard: 20,0). + Funktioniert nur, wenn „Über-die-Schulter-Ansicht“ aktiviert ist. previewIfStandStill: "Vorschau bei Stillstand" previewIfStandStillDescription: > - Verhindert dass sich der Spielercharakter in die Kamerarichtung dreht, während er untätig ist und seine Waffe weggesteckt hat. + Falls aktiviert, dreht sich der Spielercharakter nicht in Blickrichtung der Kamera, solange er untätig ist und seine Waffe weggesteckt ist. -deferredPreviewRotation: "Verzögerte Drehung der Vorschau" +deferredPreviewRotation: "Sanfter Übergang aus der Vorschau" deferredPreviewRotationDescription: | - Wenn diese Einstellung aktiv ist, dreht sich die Figur nach dem Verlassen des Vorschau- oder Vanity-Modus sanft in die Blickrichtung. - Wenn deaktiviert, dreht sich die Kamera und nicht der Spielercharakter. + Falls aktiviert, dreht sich der Spielercharakter nach dem Verlassen des Vorschau- oder Vanity-Modus sanft in die Blickrichtung der Kamera. + Falls deaktiviert, dreht sich die Kamera ruckartig in Richtung des Spielercharakters. -ignoreNC: "Ignoriere „Keine Kollision“-Variable" +ignoreNC: "Ignoriere „No Collision“-Flag" ignoreNCDescription: > - Verhindert dass die Kamera sich durch Objekte bewegt bei denen das NC-Flag (No Collision) im NIF-Modell aktiviert ist. + Falls aktiviert, bewegt sich die Kamera nicht durch Objekte, deren NC-Flag („No Collision“-Flag) im NIF-Modell aktiviert ist. move360: "360°-Bewegung" move360Description: > - Macht die Bewegungsrichtung unabhängig von der Kamerarichtung, während die Waffe des Spielercharakters nicht gezogen ist. - Zum Beispiel schaut der Spielercharakter in die Kamera, während er rückwärts läuft. + Falls aktiviert, ist die Bewegungsrichtung des Spielercharakters bei weggesteckter Waffe unabhängig von der Kamerarichtung. + Beispiel: Läuft der Spielercharakter in diesem Modus rückwärts, dreht er sich stattdessen mit dem Gesicht zur Kamera. -move360TurnSpeed: "360°-Bewegungs-Drehgeschwindigkeit" -move360TurnSpeedDescription: "Drehgeschwindigkeitsmultiplikator (Standard: 5.0)." +move360TurnSpeed: "Multiplikator für 360°-Bewegung-Drehgeschwindigkeit" +move360TurnSpeedDescription: "Multiplikator für die Drehgeschwindigkeit bei aktivierter „360°-Bewegung“ (Standard: 5,0)." slowViewChange: "Sanfter Ansichtswechsel" -slowViewChangeDescription: "Macht den Übergang von der Ego-Perspektive zur Dritte-Person-Perspektive nicht augenblicklich." +slowViewChangeDescription: "Falls aktiviert, wechselt die Kamera nicht mehr ruckartig von der Ego- in die Verfolgerperspektive." -povAutoSwitch: "Automatischer Wechsel in Ego-Perspektive" -povAutoSwitchDescription: "Wechselt automatisch in die Ego-Perspektive wenn sich direkt hinter dem Spielercharakter ein Hindernis befindet." +povAutoSwitch: "Automatischer Wechsel zur Egoperspektive" +povAutoSwitchDescription: "Falls aktiviert, wechselt die Kamera automatisch in die Egoperspektive, sobald sich sich ein Hindernis direkt hinter dem Spielercharakter befindet." -headBobbingSettings: "Kopfbewegungen in der Ego-Perspektive" +headBobbingSettings: "Kopfwippen in der Egoperspektive" headBobbing_enabled: "Eingeschaltet" -headBobbing_enabledDescription: "" +headBobbing_enabledDescription: "Falls aktiviert, wippt die Kamera beim Laufen in der Egoperspektive auf und ab." -headBobbing_step: "Schrittweite" -headBobbing_stepDescription: "Die Länge jedes Schritts (Standard: 90.0)." +headBobbing_step: "Schrittlänge" +headBobbing_stepDescription: "Definiert die Länge eines Schritts, also die Frequenz des Kopfwippens (Standard: 90,0)." headBobbing_height: "Schritthöhe" -headBobbing_heightDescription: "Die Amplitude der Kopfbewegung (Standard: 3.0)." +headBobbing_heightDescription: "Definiert die Höhe eines Schritts, also die Amplitude des Kopfwippens (Standard: 3,0)." headBobbing_roll: "Maximaler Rollwinkel" -headBobbing_rollDescription: "Der maximale Rollwinkel in Grad (Standard: 0.2)." - +headBobbing_rollDescription: "Der maximale Rollwinkel der Kamera beim Kopfwippen in Grad (Standard: 0,2)." \ No newline at end of file diff --git a/files/data/l10n/OMWControls/de.yaml b/files/data/l10n/OMWControls/de.yaml new file mode 100644 index 0000000000..db757e9165 --- /dev/null +++ b/files/data/l10n/OMWControls/de.yaml @@ -0,0 +1,86 @@ +# Source for most of the setting names: German Morrowind GOTY version + +ControlsPage: "OpenMW-Steuerung" +ControlsPageDescription: "Zusätzliche Einstellungen für Spielereingaben" + +MovementSettings: "Fortbewegung" + +alwaysRun: "Immer rennen" +alwaysRunDescription: | + Falls aktiviert, rennt der Spielercharakter standardmäßig; falls deaktiviert, geht er standardmäßig. + Gedrückthalten der Umschalttaste („Shift“) invertiert das aktuelle Verhalten vorübergehend, Drücken der Feststelltaste („Caps Lock“) schaltet das aktuelle Verhalten um. + +toggleSneak: "Schleichen umschalten" +toggleSneakDescription: | + Falls aktiviert, schaltet die „Schleichen“-Taste den Schleichen-Modus dauerhaft ein bzw. aus; falls deaktiviert, muss die „Schleichen“-Taste gedrückt gehalten werden, um im Schleichen-Modus zu bleiben. + Diese Option ist vor allem für Spieler mit schleichlastigen Spielercharakteren zu empfehlen. + +smoothControllerMovement: "Sanfte Controller-Bewegung" +smoothControllerMovementDescription: | + Aktiviert die sanfte Controller-Steuerung mit den Analog-Sticks. Dies macht den Übergang zwischen gehen und rennen weniger abrupt. + +TogglePOV_name: "Perspektive umschalten" +TogglePOV_description: "Zwischen Ego- und Verfolgerperspektive umschalten. Gedrückt halten, um den Vorschau-Modus zu aktivieren." + +Zoom3rdPerson_name: "Hinein-/Herauszoomen" +Zoom3rdPerson_description: "Bewegt die Kamera in Verfolgerperspektive näher an den Spielercharakter oder weiter von ihm weg." + +MoveForward_name: "Vorwärts" +MoveForward_description: "Kann sich mit „Rückwärts“-Eingabe aufheben." + +MoveBackward_name: "Rückwärts" +MoveBackward_description: "Kann sich mit „Vorwärts“-Eingabe aufheben." + +MoveLeft_name: "Links" +MoveLeft_description: "Kann sich mit „Rechts“-Eingabe aufheben." + +MoveRight_name: "Rechts" +MoveRight_description: "Kann sich mit „Links“-Eingabe aufheben." + +Use_name: "Benutzen" +Use_description: "Abhängig von der Haltung des Spielercharakters mit einer Waffe angreifen, einen Zauber wirken oder ein Objekt verwenden bzw. aktivieren." + +Run_name: "Rennen" +Run_description: "Gedrückt halten, um den Spielercharakter abhängig vom „Immer rennen“-Status gehen bzw. rennen zu lassen." + +AlwaysRun_name: "Immer rennen" +AlwaysRun_description: "„Immer rennen“-Status umschalten." + +Jump_name: "Sprung" +Jump_description: "Springen, solange sich der Spielercharakter auf dem Boden befindet." + +AutoMove_name: "Automatische Vorwärts-Bewegung" +AutoMove_description: "„Automatische Bewegung“-Status umschalten. Falls aktiviert, bewegt sich der Spielercharakter kontinuierlich vorwärts." + +Sneak_name: "Schleichen" +Sneak_description: "Gedrückt halten, um bei ausgeschalteter „Schleichen umschalten“-Option zu schleichen." + +ToggleSneak_name: "Schleichen umschalten" +ToggleSneak_description: "Bei eingeschalteter „Schleichen umschalten“-Option den Schleichen-Modus umschalten." + +ToggleWeapon_name: "Waffe ziehen" +ToggleWeapon_description: "Kampfhaltung einnehmen bzw. verlassen." + +ToggleSpell_name: "Zauber vorbereiten" +ToggleSpell_description: "Zauberhaltung einnehmen bzw. verlassen." + +Inventory_name: "Menü-Modus" +Inventory_description: "Inventar-Modus öffnen bzw. schließen." + +Journal_name: "Tagebuch" +Journal_description: "Tagebuch öffnen bzw. schließen." + +QuickKeysMenu_name: "Kurzmenü" +QuickKeysMenu_description: "Kurzmenü für Schnellauswahl von Gegenständen und Zaubern öffnen bzw. schließen." + +SmoothMoveForward_name: "Sanfte Vorwärts-Bewegung" +SmoothMoveForward_description: "Angepasste Vorwärts-Bewegung für aktivierte „Sanfte Controller-Bewegung“-Option" + +SmoothMoveBackward_name: "Sanfte Rückwärts-Bewegung" +SmoothMoveBackward_description: "Angepasste Rückwärts-Bewegung für aktivierte „Sanfte Controller-Bewegung“-Option" + +SmoothMoveLeft_name: "Sanfte Bewegung nach links" +SmoothMoveLeft_description: "Angepasste Bewegung nach links für aktivierte „Sanfte Controller-Bewegung“-Option" + +SmoothMoveRight_name: "Sanfte Bewegung nach rechts" +SmoothMoveRight_description: "Angepasste Bewegung nach rechts für aktivierte „Sanfte Controller-Bewegung“-Option" \ No newline at end of file diff --git a/files/data/l10n/OMWEngine/de.yaml b/files/data/l10n/OMWEngine/de.yaml index 142ab44994..3b583bfb21 100644 --- a/files/data/l10n/OMWEngine/de.yaml +++ b/files/data/l10n/OMWEngine/de.yaml @@ -1,114 +1,142 @@ # Console -#-- To be translated -#ConsoleWindow: "Console" +ConsoleWindow: "Kommandokonsole" # Debug window -DebugWindow: "Debug" -LogViewer: "Protokollansicht" +DebugWindow: "Debug-Fenster" +LogViewer: "Log-Ansicht" LuaProfiler: "Lua-Profiler" PhysicsProfiler: "Physik-Profiler" # Messages -BuildingNavigationMesh: "Baue Navigationsgitter" - -#-- To be translated -#AskLoadLastSave: "The most recent save is '%s'. Do you want to load it?" -#InitializingData: "Initializing Data..." -#LoadingExterior: "Loading Area" -#LoadingFailed: "Failed to load saved game" -#LoadingInterior: "Loading Area" -#LoadingInProgress: "Loading Save Game" -#LoadingRequiresNewVersionError: |- -# This save file was created using a newer version of OpenMW and is thus not supported. -# Please upgrade to the newest OpenMW version to load this file. -# LoadingRequiresOldVersionError: |- -# This save file was created using an older version of OpenMW in a format that is no longer supported. -# Load and save this file using {version} to upgrade it. -#NewGameConfirmation: "Do you want to start a new game and lose the current one?" -#QuitGameConfirmation: "Quit the game?" -#SaveGameDenied: "The game cannot be saved right now." -#SavingInProgress: "Saving..." -#ScreenshotFailed: "Failed to save screenshot" -#ScreenshotMade: "%s has been saved" +AskLoadLastSave: "Der aktuellste Spielstand ist '%s'. Soll er geladen werden?" +BuildingNavigationMesh: "Erstelle Navigationsgitter..." +InitializingData: "Initialisiere Daten..." +LoadingExterior: "Lade Bereich..." +LoadingFailed: "Laden des Spielstandes fehlgeschlagen!" +LoadingInterior: "Lade Bereich..." +LoadingInProgress: "Lade Spielstand..." +LoadingRequiresNewVersionError: |- + Der Spielstand wurde mit einer neueren Version von OpenMW erstellt und wird daher nicht unterstützt. + Bitte aktualisieren Sie Ihre OpenMW-Installation, um den Spielstand laden zu können. +LoadingRequiresOldVersionError: |- + Der Spielstand wurde mit einer älteren Version von OpenMW erstellt, deren Speicherformat nicht mehr unterstützt wird. + Laden und speichern Sie den Spielstand mit Version {version}, um das Format anzupassen. +NewGameConfirmation: "Neues Spiel starten und aktuellen Fortschritt verwerfen?" +QuitGameConfirmation: "Spiel verlassen?" +SaveGameDenied: "Das Spiel kann gerade nicht gespeichert werden!" +SavingInProgress: "Speichere Spiel..." +ScreenshotFailed: "Speichern des Screenshots fehlgeschlagen!" +ScreenshotMade: "%s wurde gespeichert." # Save game menu -SelectCharacter: "Charakterauswahl..." +DeleteGame: "Spielstand löschen" +DeleteGameConfirmation: "Möchten Sie den Spielstand wirklich löschen?" +EmptySaveNameError: "Der Spielstand kann ohne Name nicht gespeichert werden!" +LoadGameConfirmation: "Spielstand laden und aktuellen Fortschritt verwerfen?" +MissingContentFilesConfirmation: |- + Die aktuell ausgewählten Spieledateien stimmen nicht mit den im Spielstand verwendeten überein, + was zu Fehlern beim Laden und während des Spielens führen kann. + Möchten Sie wirklich fortfahren? +MissingContentFilesList: |- + {files, plural, + one{\n\nEine fehlende Datei gefunden: } + few{\n\n{files} fehlende Dateien gefunden:\n} + other{\n\n{files} fehlende Dateien gefunden:\n} + } +MissingContentFilesListCopy: |- + {files, plural, + one{\n\nDrücken Sie „Kopieren“, um den Namen in die Zwischenablage zu kopieren.} + few{\n\nDrücken Sie „Kopieren“, um alle Namen in die Zwischenablage zu kopieren.} + other{\n\nDrücken Sie „Kopieren“, um alle Namen in die Zwischenablage zu kopieren.} + } +OverwriteGameConfirmation: "Sind Sie sicher, dass Sie den Spielstand überschreiben wollen?" +SelectCharacter: "Charakter auswählen..." TimePlayed: "Spielzeit" -#-- To be translated -#DeleteGame: "Delete Game" -#DeleteGameConfirmation: "Are you sure you want to delete this saved game?" -#EmptySaveNameError: "Game can not be saved without a name!" -#LoadGameConfirmation: "Do you want to load a saved game and lose the current one?" -#MissingContentFilesConfirmation: |- -# The currently selected content files do not match the ones used by this save game. -# Errors may occur during load or game play. -# Do you wish to continue? -#MissingContentFilesList: |- -# {files, plural, -# one{\n\nFound missing file: } -# few{\n\nFound {files} missing files:\n} -# other{\n\nFound {files} missing files:\n} -# } -#MissingContentFilesListCopy: |- -# {files, plural, -# one{\n\nPress Copy to place its name to the clipboard.} -# few{\n\nPress Copy to place their names to the clipboard.} -# other{\n\nPress Copy to place their names to the clipboard.} -# } -#OverwriteGameConfirmation: "Are you sure you want to overwrite this saved game?" - # Settings menu ActorsProcessingRange: "Akteur-Verarbeitungsreichweite" Anisotropy: "Anisotropie" +Audio: "Audio" +AudioMaster: "Master-Lautstärke" +AudioVoice: "Stimmen" +AudioEffects: "Effekte" +AudioFootsteps: "Schritte" +AudioMusic: "Musik" CameraSensitivity: "Kameraempfindlichkeit" CameraZoomIn: "Kamera hineinzoomen" CameraZoomOut: "Kamera herauszoomen" -ChangeRequiresRestart: "Diese Änderung erfordert einen Neustart um wirksam zu werden." +ChangeRequiresRestart: "Diese Änderung erfordert einen Neustart, um wirksam zu werden." +ConfirmResetBindings: "Alle Tastenbelegungen zurücksetzen?" +ConfirmResolution: "Neue Auflösung wird sofort angewendet. Fortsetzen?" Controller: "Controller" +Controls: "Steuerung" +DelayLow: "Schnell" +DelayHigh: "Langsam" +DetailLevel: "Detailgrad" +Difficulty: "Schwierigkeitsgrad" +DifficultyEasy: "Leicht" +DifficultyHard: "Schwer" +DistanceHigh: "Weit" +DistanceLow: "Kurz" +EnableController: "Controller aktivieren" FieldOfView: "Sichtfeld" -FrameRateHint: "Hinweis: Drücken Sie F3, um die aktuelle Bildrate\nanzuzeigen." -InvertXAxis: "X-Achse umkehren" +FieldOfViewLow: "Niedrig" +FieldOfViewHigh: "Hoch" +FrameRateHint: "Hinweis: Drücken Sie F3,\num die aktuelle Bildrate anzuzeigen." +GammaCorrection: "Gamma-Korrektur" +GammaDark: "Dunkel" +GammaLight: "Hell" +GmstOverridesL10n: "Texte aus ESM-Dateien haben Vorrang" +InvertXAxis: "X-Achse invertieren" +InvertYAxis: "Y-Achse invertieren" Language: "Sprache" -LanguageNote: "Hinweis: Diese Einstellungen wirkt sich nicht auf Text von ESM-Dateien aus." +LanguageNote: "Hinweis: Diese Einstellungen wirken sich nicht auf Texte aus ESM-Dateien aus." LightingMethod: "Beleuchtungsmethode" LightingMethodLegacy: "Veraltet" LightingMethodShaders: "Shader" -LightingMethodShadersCompatibility: "Shader (Kompatibilität)" -LightingResetToDefaults: "Setzt auf Standardwerte zurück; möchten Sie fortfahren? Änderungen an der Beleuchtungsmethode erfordern einen Neustart." +LightingMethodShadersCompatibility: "Shader (Kompatibilitätsmodus)" +LightingResetToDefaults: "Einstellungen wirklich zurücksetzen? Das Ändern der „Beleuchtungsmethode“ erfordert einen Neustart." Lights: "Beleuchtung" -LightsBoundingSphereMultiplier: "Bounding-Sphere-Multiplikator" -LightsBoundingSphereMultiplierTooltip: "Standard: 1.65\nMultiplikator für Begrenzungskugel.\nHöhere Zahlen ermöglichen einen sanften Abfall, erfordern jedoch eine Erhöhung der Anzahl der maximalen Lichter.\n\nBeeinflusst nicht die Beleuchtung oder Lichtstärke." -LightsFadeStartMultiplier: "Licht Verblassungs-Start-Multiplikator" -LightsFadeStartMultiplierTooltip: "Standard: 0.85\nBruchteil der maximalen Entfernung, bei der die Lichter zu verblassen beginnen.\n\nStellen Sie hier einen niedrigen Wert für langsamere Übergänge oder einen hohen Wert für schnellere Übergänge ein." -#LightsLightingMethodTooltip: "Set the internal handling of light sources.\n\n -# \"Legacy\" always uses 8 lights per object and provides a lighting closest to an original game.\n\n -# \"Shaders (compatibility)\" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.\n\n -# \"Shaders\" carries all of the benefits that \"Shaders (compatibility)\" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware." +LightsBoundingSphereMultiplier: "Multiplikator für Begrenzungssphäre" +LightsBoundingSphereMultiplierTooltip: "Standard: 1,65\nMultiplikator für Größe der Begrenzungssphäre.\nGrößere Werte ermöglichen einen sanfteren Abfall, erfordern jedoch eine Erhöhung der maximalen Anzahl von Lichtquellen.\n\nBeeinflusst nicht die Beleuchtung oder Lichtintensität." +LightsFadeStartMultiplier: "Multiplikator für Startwert der Lichtabblendung" +LightsFadeStartMultiplierTooltip: "Standard: 0,85\nBruchteil der maximalen Lichtreichweite, bei der Lichtquellen langsam zu verblassen beginnen.\n\nKleinere Werte führen zu einem sanfteren Übergang, der allerdings bereits bei geringerer Entfernung startet; größere Werte machen den Übergang abrupter, betreffen aber nur weiter entfernte Lichtquellen." +LightsLightingMethodTooltip: "Legt die interne Behandlung von Lichtquellen fest.\n\n + „Veraltet“ verwendet immer bis zu 8 Lichtquellen pro Objekt und führt zu Ergebnissen, die denen der Original-Engine am ähnlichsten sind.\n\n + „Shader (Kompatibilitätsmodus)“ entfernt das Maximum von 8 Lichtquellen pro Objekt; Bodenvegetation wird von Lichtquellen beleuchtet und das sanfte Abblenden von Lichtquellen wird aktiviert. Es wird empfohlen, diese Option für ältere Hardware und Maximalwerte für Lichtquellen nahe 8 zu verwenden.\n\n + „Shader“ bietet alle Vorteile von „Shader (Kompatibilitätsmodus)“, nutzt aber einen moderneren Ansatz, der größere Maximalwerte für Lichtquellen bei geringen bis keinen Leistungseinbußen ermöglicht. Funktioniert möglicherweise nicht auf älterer Hardware." LightsMaximumDistance: "Maximale Lichtreichweite" -LightsMaximumDistanceTooltip: "Standard: 8192\nMaximale Entfernung, bei der Lichter erscheinen (gemessen in Einheiten).\n\nSetzen Sie dies auf 0, um eine unbegrenzte Entfernung zu verwenden." -LightsMinimumInteriorBrightness: "Minimale Innenhelligkeit" -LightsMinimumInteriorBrightnessTooltip: "Standard: 0.08\nMinimale Umgebungshelligkeit im Innenraum.\n\nErhöhen Sie dies, wenn Sie das Gefühl haben, dass Innenräume zu dunkel sind." -MaxLights: "Maximale Anzahl an Lichtern" -MaxLightsTooltip: "Standard: 8\nMaximale Anzahl von Lichtern pro Objekt.\n\nEine niedrige Zahl in der Nähe des Standardwerts führt zu Lichtsprüngen, ähnlich wie bei klassischer Beleuchtung." +LightsMaximumDistanceTooltip: "Standard: 8192 (1 Zelle)\nMaximale Entfernung, bis zu der Lichtquellen noch dargestellt werden (gemessen in In-Game-Einheiten).\n\nEin Wert von 0 entspricht einer unbegrenzten Reichweite." +LightsMinimumInteriorBrightness: "Minimale Helligkeit in Innenräumen" +LightsMinimumInteriorBrightnessTooltip: "Standard: 0,08\nMinimale Umgebungshelligkeit in Innenräumen.\n\nKann erhöht werden, falls Innenräume (v.a. bei Shader-Beleuchtungsmethoden) zu dunkel dargestellt werden." +MaxLights: "Maximale Anzahl von Lichtquellen pro Objekt" +MaxLightsTooltip: "Standard: 8\nMaximale Anzahl von Lichtquellen, die ein Objekt beleuchten können.\n\nKleine Werte können gerade an Orten mit vielen Lichtquellen zum Aufploppen und zum schnellen Wechsel von Lichtern führen, wie es aus der Original-Engine und anderen The-Elder-Scrolls-Titeln bekannt ist." +MenuHelpDelay: "Verzögerung des Hilfe-Menüs" +MenuTransparency: "Menü-Transparenz" MouseAndKeyboard: "Maus/Tastatur" -PostProcessing: "Nachbearbeitung" -PostProcessingTooltip: "Optimiert über Nachbearbeitungs-HUD, siehe Eingabeeinstellungen." +PostProcessing: "Post-Processing" +PostProcessingIsNotEnabled: "Post-Processing ist nicht aktiviert!" +PostProcessingTooltip: "Wird über Post-Processing-HUD konfiguriert (siehe Tastenbelegung)." +Preferences: "Einstellungen" PrimaryLanguage: "Hauptsprache" PrimaryLanguageTooltip: "Lokalisierungsdateien für diese Sprache haben die höchste Priorität." -RainRippleDetail: "Detail der Regenkräuselung" -RainRippleDetailDense: "Dicht" -RainRippleDetailSimple: "Einfach" -RainRippleDetailSparse: "Spärlich" -ReflectionShaderDetail: "Reflexions-Shader-Detail" +QualityHigh: "Hoch" +QualityLow: "Niedrig" +QualityMedium: "Mittel" +RainRippleDetail: "Detailgrad der Regentropfen-Wellen" +RainRippleDetailDense: "Dicht (mit Normal-Maps)" +RainRippleDetailSimple: "Einfach (keine Normal-Maps)" +RainRippleDetailSparse: "Spärlich (mit Normal-Maps)" +RebindAction: "Drücken Sie eine Taste oder einen Knopf, um diese Tastenbelegung zu ändern." +ReflectionShaderDetail: "Detailgrad des Wasserreflexions-Shaders" ReflectionShaderDetailActors: "Akteure" ReflectionShaderDetailGroundcover: "Bodenvegetation" ReflectionShaderDetailObjects: "Objekte" @@ -116,69 +144,40 @@ ReflectionShaderDetailSky: "Himmel" ReflectionShaderDetailTerrain: "Terrain" ReflectionShaderDetailWorld: "Welt" Refraction: "Lichtbrechung" -Screenshot: "Bildschirmfoto" +ResetControls: "Tastenbelegungen zurücksetzen" +Screenshot: "Screenshot" Scripts: "Skripte" +ScriptsDisabled: "Laden Sie einen Spielstand, um auf die Skripteinstellungen zugreifen zu können." SecondaryLanguage: "Sekundäre Sprache" -SecondaryLanguageTooltip: "Lokalisierungsdateien für diese Sprache können verwendet werden, wenn bei der primären Sprachdateien die erforderlichen Zeilen fehlen." -TextureFiltering: "Texturfilterung" +SecondaryLanguageTooltip: "Lokalisierungsdateien für diese Sprache werden als Rückfalloption verwendet, falls in den Dateien der Primärsprache die erforderlichen Zeilen fehlen." +SensitivityHigh: "Hoch" +SensitivityLow: "Niedrig" +SettingsWindow: "Optionen" +Subtitles: "Untertitel" +SunlightScattering: "Sonnenlicht-Streuung" +TestingExteriorCells: "Außenzellen testen" +TestingInteriorCells: "Innenzellen testen" +TextureFiltering: "Texturfilter" TextureFilteringBilinear: "Bilinear" -TextureFilteringDisabled: "Nichts" +TextureFilteringDisabled: "Keiner" TextureFilteringOther: "Sonstiges" TextureFilteringTrilinear: "Trilinear" ToggleHUD: "HUD umschalten" -TogglePostProcessorHUD: "Nachbearbeitungs-HUD umschalten" +TogglePostProcessorHUD: "Post-Processing-HUD umschalten" +TransparencyFull: "Vollständig" +TransparencyNone: "Keine" +Video: "Video" +ViewDistance: "Sichtweite" VSync: "VSync" +VSyncAdaptive: "Adaptiv" Water: "Wasser" -WaterShader: "Wasser Shader" +WaterShader: "Wasser-Shader" WaterShaderTextureQuality: "Texturqualität" -WindowBorder: "Fensterrand" +WindowBorder: "Fensterrahmen darstellen" WindowMode: "Fenstermodus" WindowModeFullscreen: "Vollbild" +WindowModeHint: "Hinweis: Die Option „Fenster in Vollbildgröße“\nverwendet automatisch die native Bildschirmauflösung." WindowModeWindowed: "Fenster" -WindowModeWindowedFullscreen: "Fenster Vollbild" - -#-- To be translated -#Audio: "Audio" -#AudioMaster: "Master" -#AudioVoice: "Voice" -#AudioEffects: "Effects" -#AudioFootsteps: "Footsteps" -#AudioMusic: "Music" -#ConfirmResetBindings: "Reset all controls to the default?" -#ConfirmResolution: "New resolution will be applied immediately. Do you want to continue?" -#Controls: "Controls" -#DelayLow: "Fast" -#DelayHigh: "Slow" -#DetailLevel: "Detail Level" -#Difficulty: "Difficulty" -#DifficultyEasy: "Easy" -#DifficultyHard: "Hard" -#DistanceHigh: "Far" -#DistanceLow: "Near" -#EnableController: "Enable Controller" -#FieldOfViewLow: "Low" -#FieldOfViewHigh: "High" -#GammaCorrection: "Gamma Correction" -#GammaDark: "Dark" -#GammaLight: "Light" -#GmstOverridesL10n: "Strings From ESM Files Have Priority" -#InvertYAxis: "Invert Y Axis" -#MenuHelpDelay: "Menu Help Delay" -#MenuTransparency: "Menu Transparency" -#Preferences: "Prefs" -#PostProcessingIsNotEnabled -#QualityHigh: "High" -#QualityLow: "Low" -#QualityMedium: "Medium" -#RebindAction: "Press a key or button to rebind this control." -#ResetControls: "Reset Controls" -#SensitivityHigh: "High" -#SensitivityLow: "Low" -#SettingsWindow: "Options" -#Subtitles: "Subtitles" -#TestingExteriorCells: "Testing exterior cells" -#TestingInteriorCells: "Testing interior cells" -#TransparencyFull: "Full" -#TransparencyNone: "None" -#Video: "Video" -#ViewDistance: "View Distance" +WindowModeWindowedFullscreen: "Fenster in Vollbildgröße" +# More fitting translations of "wobbly" are welcome +WobblyShores: "Wabbelige Uferlinien" \ No newline at end of file diff --git a/files/data/l10n/OMWShaders/de.yaml b/files/data/l10n/OMWShaders/de.yaml index 7d17199dd9..7ea5eb59e3 100644 --- a/files/data/l10n/OMWShaders/de.yaml +++ b/files/data/l10n/OMWShaders/de.yaml @@ -1,6 +1,6 @@ # Post-processing HUD -Abovewater: "Überwasser" +Abovewater: "Über Wasser" ActiveShaders: "Aktive Shader" Author: "Autor" Description: "Beschreibung" @@ -8,27 +8,66 @@ InactiveShaders: "Inaktive Shader" InExteriors: "Außenbereich" InInteriors: "Innenbereich" KeyboardControls: | - Tastatursteuerung:: + Tastatursteuerung: Shift+Pfeil-Rechts > Aktiviere Shader Shift+Pfeil-Links > Deaktiviere Shader - Shift+Pfeil-Hoch > Verschiebe Shader nach oben - Shift+Pfeil-Runter > Verschiebe Shader nach unten -PostProcessHUD: "Nachbearbeitungs HUD" -ResetShader: "Setze Shader auf Standardzustand zurück" -ShaderLocked: "Verschlossen" -ShaderLockedDescription: "Kann nicht umgeschaltet oder verschoben werden, gesteuert durch externes Lua-Skript" + Shift+Pfeil-Hoch > Verschiebe Shader in Liste nach oben + Shift+Pfeil-Runter > Verschiebe Shader in Liste nach unten +# Better avoid the German translation "Nachbearbeitung" as it might lead to confusion. +PostProcessHUD: "Post-Processing-HUD" +ResetShader: "Shader zurücksetzen" +ShaderLocked: "Gesperrt" +ShaderLockedDescription: "Shader kann nicht umgeschaltet oder verschoben werden, sondern wird durch externes Lua-Skript gesteuert." ShaderResetUniform: "r" -Underwater: "Unterwasser" +Underwater: "Unter Wasser" Version: "Version" # Built-in post-processing shaders -DisplayDepthName: "Visualisiert den Tiefenpuffer." -DisplayDepthFactorDescription: "Bestimmt die Korrelation zwischen dem Pixeltiefenwert und seiner Ausgabefarbe. Hohe Werte führen zu einem helleren Bild." -DisplayDepthFactorName: "Farbfaktor" -ContrastLevelDescription: "Kontraststufe" +# Adjustments +# +AdjustmentsDescription: "Farbanpassungen" +# "Contrast Level" +ContrastLevelDescription: "Kontrast-Wert" ContrastLevelName: "Kontrast" -GammaLevelDescription: "Gamma-Level" +# "Gamma Level" GammaLevelName: "Gamma" +GammaLevelDescription: "Gamma-Wert" + +# Bloom +# +BloomDescription: "Bloom-Shader, der seine Berechnungen bei ungefähr linearem Licht durchführt." +# "Bloom Clamp Level" +BloomClampLevelName: "Oberer Grenzwert" +BloomClampLevelDescription: "Begrenze die Helligkeit einzelner Pixel auf diesen Wert, bevor Bloom in die Szene gemischt wird." +# "Bloom Threshold Level" +BloomThresholdLevelName: "Unterer Schwellenwert" +BloomThresholdLevelDescription: "Ignoriere Pixel, deren Helligkeit geringer als dieser Wert ist, bei der Berechnung der Bloom-Unschärfe. Dies wirkt sich nicht auf den Himmel aus." +# "Gamma Level" +# (see above) +# "Radius Level" +RadiusLevelName: "Radius" +RadiusLevelDescription: "Radius des Effektes" +# "Sky Factor Level" +SkyFactorLevelName: "Himmel-Faktor" +SkyFactorLevelDescription: "Multiplikator für Licht, das direkt vom Himmel kommt." +# "Strength Level" +StrengthLevelName: "Strength" +StrengthLevelDescription: "Stärke des Effektes" + + +# Debug +# +DebugDescription: "Debug-Shader" +# "Display Depth" +DebugHeaderDepth: "Tiefenpuffer (Z-Buffer)" +DisplayDepthName: "Visualisiere den Tiefenpuffer (Z-Puffer)" +# "Depth Factor" +DisplayDepthFactorName: "Faktor für Tiefenfärbung" +DisplayDepthFactorDescription: "Verknüpfungsfaktor zwischen dem Tiefenwert eines Pixels und der ausgegebenen Farbe. Größere Werte führen zu einem helleren Bild." +# "Display Normals" +DebugHeaderNormals: "Normalenvektoren" +DisplayNormalsName: "Visualisiere Normalenvektoren" +NormalsInWorldSpace: "Zeige Normalenvektoren in Spielwelt-Koordinaten" \ No newline at end of file From 58afe1ba23fe7c0fafd4e163fedf161ffd6bf9a1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 23 Mar 2024 19:46:31 +0300 Subject: [PATCH 392/451] Support red-green normal maps --- apps/opencs/model/world/data.cpp | 1 + apps/openmw/mwrender/renderingmanager.cpp | 1 + components/resource/imagemanager.cpp | 3 +-- components/shader/shadervisitor.cpp | 20 ++++++++++++++++++++ components/shader/shadervisitor.hpp | 1 + files/shaders/compatibility/bs/default.frag | 3 +++ files/shaders/compatibility/groundcover.frag | 6 +++++- files/shaders/compatibility/objects.frag | 6 +++++- files/shaders/compatibility/terrain.frag | 6 +++++- 9 files changed, 42 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 470ce04131..4198e1b980 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -152,6 +152,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mResourceSystem = std::make_unique(mVFS.get(), expiryDelay, &mEncoder.getStatelessEncoder()); + // FIXME: this is severely out of date (see #7595) Shader::ShaderManager::DefineMap defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); Shader::ShaderManager::DefineMap shadowDefines = SceneUtil::ShadowManager::getShadowsDisabledDefines(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index dc71e455b2..d5c5321f8e 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -446,6 +446,7 @@ namespace MWRender globalDefines["useOVR_multiview"] = "0"; globalDefines["numViews"] = "1"; globalDefines["disableNormals"] = "1"; + globalDefines["reconstructNormalZ"] = "0"; for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++) globalDefines[itr->first] = itr->second; diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index a7d2ef61a1..e7cc9f03e5 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -78,8 +78,7 @@ namespace Resource } break; } - // not bothering with checks for other compression formats right now, we are unlikely to ever use those - // anyway + // not bothering with checks for other compression formats right now default: return true; } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 7bce9de2a6..3867f1f43e 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -184,6 +184,7 @@ namespace Shader , mAdditiveBlending(false) , mDiffuseHeight(false) , mNormalHeight(false) + , mReconstructNormalZ(false) , mTexStageRequiringTangents(-1) , mSoftParticles(false) , mNode(nullptr) @@ -429,6 +430,7 @@ namespace Shader normalMapTex->setFilter(osg::Texture::MAG_FILTER, diffuseMap->getFilter(osg::Texture::MAG_FILTER)); normalMapTex->setMaxAnisotropy(diffuseMap->getMaxAnisotropy()); normalMapTex->setName("normalMap"); + normalMap = normalMapTex; int unit = texAttributes.size(); if (!writableStateSet) @@ -440,6 +442,23 @@ namespace Shader mRequirements.back().mNormalHeight = normalHeight; } } + + if (normalMap != nullptr && normalMap->getImage(0)) + { + // Special handling for red-green normal maps (e.g. BC5 or R8G8). + switch (normalMap->getImage(0)->getPixelFormat()) + { + case GL_RG: + case GL_RG_INTEGER: + case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: + case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: + { + mRequirements.back().mReconstructNormalZ = true; + mRequirements.back().mNormalHeight = false; + } + } + } + if (mAutoUseSpecularMaps && diffuseMap != nullptr && specularMap == nullptr && diffuseMap->getImage(0)) { std::string specularMapFileName = diffuseMap->getImage(0)->getFileName(); @@ -629,6 +648,7 @@ namespace Shader defineMap["diffuseParallax"] = reqs.mDiffuseHeight ? "1" : "0"; defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; + defineMap["reconstructNormalZ"] = reqs.mReconstructNormalZ ? "1" : "0"; writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); addedState->addUniform("colorMode"); diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index a8e79ec995..9ce0819bd3 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -110,6 +110,7 @@ namespace Shader bool mDiffuseHeight; // true if diffuse map has height info in alpha channel bool mNormalHeight; // true if normal map has height info in alpha channel + bool mReconstructNormalZ; // used for red-green normal maps (e.g. BC5) // -1 == no tangents required int mTexStageRequiringTangents; diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index 77131c6a52..d2c8de0b22 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -77,6 +77,9 @@ void main() vec3 specularColor = getSpecularColor().xyz; #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); +#if @reconstructNormalZ + normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); +#endif vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); specularColor *= normalTex.a; #else diff --git a/files/shaders/compatibility/groundcover.frag b/files/shaders/compatibility/groundcover.frag index dfdd6518c3..aab37d465d 100644 --- a/files/shaders/compatibility/groundcover.frag +++ b/files/shaders/compatibility/groundcover.frag @@ -59,7 +59,11 @@ void main() gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); #if @normalMap - vec3 viewNormal = normalToView(texture2D(normalMap, normalMapUV).xyz * 2.0 - 1.0); + vec4 normalTex = texture2D(normalMap, normalMapUV); +#if @reconstructNormalZ + normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); +#endif + vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); #else vec3 viewNormal = normalToView(normalize(passNormal)); #endif diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 56c7abf27c..eb5b79a0c2 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -167,7 +167,11 @@ vec2 screenCoords = gl_FragCoord.xy / screenRes; gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); #if @normalMap - vec3 viewNormal = normalToView(texture2D(normalMap, normalMapUV + offset).xyz * 2.0 - 1.0); + vec4 normalTex = texture2D(normalMap, normalMapUV + offset); +#if @reconstructNormalZ + normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); +#endif + vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); #else vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index abc7425eb0..f45f1f024e 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -63,7 +63,11 @@ void main() #endif #if @normalMap - vec3 viewNormal = normalToView(texture2D(normalMap, adjustedUV).xyz * 2.0 - 1.0); + vec4 normalTex = texture2D(normalMap, adjustedUV); +#if @reconstructNormalZ + normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); +#endif + vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); #else vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif From cf6b95ae7cf739c00fe00919bc1959b99deca515 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 23 Mar 2024 23:10:29 +0300 Subject: [PATCH 393/451] Document some technical details regarding normal-mapping --- .../modding/texture-modding/texture-basics.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/source/reference/modding/texture-modding/texture-basics.rst b/docs/source/reference/modding/texture-modding/texture-basics.rst index 78ae007704..8bbf018fba 100644 --- a/docs/source/reference/modding/texture-modding/texture-basics.rst +++ b/docs/source/reference/modding/texture-modding/texture-basics.rst @@ -25,6 +25,19 @@ Content creators need to know that OpenMW uses the DX format for normal maps, an See the section `Automatic use`_ further down below for detailed information. +The RGB channels of the normal map are used to store XYZ components of tangent space normals and the alpha channel of the normal map may be used to store a height map used for parallax. + +This is different from the setup used in Bethesda games that use the traditional pipeline, which may store specular information in the alpha channel. + +Special pixel formats that only store two color channels exist and are used by Bethesda games that employ a PBR-based pipeline. Compressed red-green formats are optimized for use with normal maps and suffer from far less quality degradation than S3TC-compressed normal maps of equivalent size. + +OpenMW supports the use of such pixel formats. When a red-green normal map is provided, the Z component of the normal will be reconstructed based on XY components it stores. +Naturally, since these formats cannot provide an alpha channel, they do not support parallax. + +Keep in mind, however, that while the necessary hardware support is widespread for compressed red-green formats, it is less ubiquitous than the support for S3TC family of compressed formats. +Should you run into the consequences of this, you might want to convert such textures into an uncompressed red-green format such as R8G8. +Be careful not to try and convert such textures into a full-color format as the previously non-existent blue channel would then be used. + Specular Mapping ################ From 3c0c1717a98951f6e0809911aaef12be3d29d37b Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 28 Mar 2024 07:43:33 +0300 Subject: [PATCH 394/451] Fix red-green normal map handling for terrain --- apps/opencs/model/world/data.cpp | 1 - apps/openmw/mwrender/renderingmanager.cpp | 1 - components/sceneutil/util.cpp | 14 ++++++++++++++ components/sceneutil/util.hpp | 2 ++ components/shader/shadervisitor.cpp | 15 +++++---------- components/terrain/material.cpp | 15 ++++++++++++++- 6 files changed, 35 insertions(+), 13 deletions(-) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 4198e1b980..470ce04131 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -152,7 +152,6 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mResourceSystem = std::make_unique(mVFS.get(), expiryDelay, &mEncoder.getStatelessEncoder()); - // FIXME: this is severely out of date (see #7595) Shader::ShaderManager::DefineMap defines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); Shader::ShaderManager::DefineMap shadowDefines = SceneUtil::ShadowManager::getShadowsDisabledDefines(); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index d5c5321f8e..dc71e455b2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -446,7 +446,6 @@ namespace MWRender globalDefines["useOVR_multiview"] = "0"; globalDefines["numViews"] = "1"; globalDefines["disableNormals"] = "1"; - globalDefines["reconstructNormalZ"] = "0"; for (auto itr = lightDefines.begin(); itr != lightDefines.end(); itr++) globalDefines[itr->first] = itr->second; diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index ab600de11d..a5629ee092 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -286,4 +286,18 @@ namespace SceneUtil mOperationQueue->add(operation); } + bool isRedGreenPixelFormat(GLenum format) + { + switch (format) + { + case GL_RG: + case GL_RG_INTEGER: + case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: + case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: + return true; + } + + return false; + } + } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 29fee09176..0f4b82bbe0 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -112,6 +112,8 @@ namespace SceneUtil protected: osg::ref_ptr mOperationQueue; }; + + bool isRedGreenPixelFormat(GLenum format); } #endif diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 3867f1f43e..600e35a22a 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -445,17 +446,11 @@ namespace Shader if (normalMap != nullptr && normalMap->getImage(0)) { - // Special handling for red-green normal maps (e.g. BC5 or R8G8). - switch (normalMap->getImage(0)->getPixelFormat()) + // Special handling for red-green normal maps (e.g. BC5 or R8G8) + if (SceneUtil::isRedGreenPixelFormat(normalMap->getImage(0)->getPixelFormat())) { - case GL_RG: - case GL_RG_INTEGER: - case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: - case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: - { - mRequirements.back().mReconstructNormalZ = true; - mRequirements.back().mNormalHeight = false; - } + mRequirements.back().mReconstructNormalZ = true; + mRequirements.back().mNormalHeight = false; } } diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index fafe2dcb58..10dbeb9838 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -271,18 +272,30 @@ namespace Terrain stateset->addUniform(UniformCollection::value().mBlendMap); } + bool parallax = it->mNormalMap && it->mParallax; + bool reconstructNormalZ = false; + if (it->mNormalMap) { stateset->setTextureAttributeAndModes(2, it->mNormalMap); stateset->addUniform(UniformCollection::value().mNormalMap); + + // Special handling for red-green normal maps (e.g. BC5 or R8G8). + const osg::Image* image = it->mNormalMap->getImage(0); + if (image && SceneUtil::isRedGreenPixelFormat(image->getPixelFormat())) + { + reconstructNormalZ = true; + parallax = false; + } } Shader::ShaderManager::DefineMap defineMap; defineMap["normalMap"] = (it->mNormalMap) ? "1" : "0"; defineMap["blendMap"] = (!blendmaps.empty()) ? "1" : "0"; defineMap["specularMap"] = it->mSpecular ? "1" : "0"; - defineMap["parallax"] = (it->mNormalMap && it->mParallax) ? "1" : "0"; + defineMap["parallax"] = parallax ? "1" : "0"; defineMap["writeNormals"] = (it == layers.end() - 1) ? "1" : "0"; + defineMap["reconstructNormalZ"] = reconstructNormalZ ? "1" : "0"; Stereo::shaderStereoDefines(defineMap); stateset->setAttributeAndModes(shaderManager.getProgram("terrain", defineMap)); From b016f414d5251e2ea7979bba6ad737487f94d920 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 15 Apr 2024 17:06:40 +0200 Subject: [PATCH 395/451] Add INFO record unit test --- apps/openmw_test_suite/esm3/testsaveload.cpp | 78 ++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index 2629442563..afffbbb3d6 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -603,6 +604,83 @@ namespace ESM EXPECT_EQ(result.mIcon, record.mIcon); } + TEST_P(Esm3SaveLoadRecordTest, infoShouldNotChange) + { + DialInfo record = { + .mData = { + .mType = ESM::Dialogue::Topic, + .mDisposition = 1, + .mRank = 2, + .mGender = ESM::DialInfo::NA, + .mPCrank = 3, + }, + .mSelects = { + ESM::DialogueCondition{ + .mVariable = {}, + .mValue = 42, + .mIndex = 0, + .mFunction = ESM::DialogueCondition::Function_Level, + .mComparison = ESM::DialogueCondition::Comp_Eq + }, + ESM::DialogueCondition{ + .mVariable = generateRandomString(32), + .mValue = 0, + .mIndex = 1, + .mFunction = ESM::DialogueCondition::Function_NotLocal, + .mComparison = ESM::DialogueCondition::Comp_Eq + }, + }, + .mId = generateRandomRefId(32), + .mPrev = generateRandomRefId(32), + .mNext = generateRandomRefId(32), + .mActor = generateRandomRefId(32), + .mRace = generateRandomRefId(32), + .mClass = generateRandomRefId(32), + .mFaction = generateRandomRefId(32), + .mPcFaction = generateRandomRefId(32), + .mCell = generateRandomRefId(32), + .mSound = generateRandomString(32), + .mResponse = generateRandomString(32), + .mResultScript = generateRandomString(32), + .mFactionLess = false, + .mQuestStatus = ESM::DialInfo::QS_None, + }; + + DialInfo result; + saveAndLoadRecord(record, GetParam(), result); + + EXPECT_EQ(result.mData.mType, record.mData.mType); + EXPECT_EQ(result.mData.mDisposition, record.mData.mDisposition); + EXPECT_EQ(result.mData.mRank, record.mData.mRank); + EXPECT_EQ(result.mData.mGender, record.mData.mGender); + EXPECT_EQ(result.mData.mPCrank, record.mData.mPCrank); + EXPECT_EQ(result.mId, record.mId); + EXPECT_EQ(result.mPrev, record.mPrev); + EXPECT_EQ(result.mNext, record.mNext); + EXPECT_EQ(result.mActor, record.mActor); + EXPECT_EQ(result.mRace, record.mRace); + EXPECT_EQ(result.mClass, record.mClass); + EXPECT_EQ(result.mFaction, record.mFaction); + EXPECT_EQ(result.mPcFaction, record.mPcFaction); + EXPECT_EQ(result.mCell, record.mCell); + EXPECT_EQ(result.mSound, record.mSound); + EXPECT_EQ(result.mResponse, record.mResponse); + EXPECT_EQ(result.mResultScript, record.mResultScript); + EXPECT_EQ(result.mFactionLess, record.mFactionLess); + EXPECT_EQ(result.mQuestStatus, record.mQuestStatus); + EXPECT_EQ(result.mSelects.size(), record.mSelects.size()); + for (size_t i = 0; i < result.mSelects.size(); ++i) + { + const auto& resultS = result.mSelects[i]; + const auto& recordS = record.mSelects[i]; + EXPECT_EQ(resultS.mVariable, recordS.mVariable); + EXPECT_EQ(resultS.mValue, recordS.mValue); + EXPECT_EQ(resultS.mIndex, recordS.mIndex); + EXPECT_EQ(resultS.mFunction, recordS.mFunction); + EXPECT_EQ(resultS.mComparison, recordS.mComparison); + } + } + INSTANTIATE_TEST_SUITE_P(FormatVersions, Esm3SaveLoadRecordTest, ValuesIn(getFormats())); } } From 963035fe47c99d9ba33f0915c6271d40760d7620 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 15 Apr 2024 18:20:57 +0200 Subject: [PATCH 396/451] Change wander package column names to match reality --- apps/opencs/model/world/columns.cpp | 2 +- apps/opencs/model/world/columns.hpp | 16 ++++++++-------- apps/opencs/model/world/refidcollection.cpp | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index bdbb8f697c..d4c35c5cec 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -324,7 +324,6 @@ namespace CSMWorld { ColumnId_MaxAttack, "Max Attack" }, { ColumnId_CreatureMisc, "Creature Misc" }, - { ColumnId_Idle1, "Idle 1" }, { ColumnId_Idle2, "Idle 2" }, { ColumnId_Idle3, "Idle 3" }, { ColumnId_Idle4, "Idle 4" }, @@ -332,6 +331,7 @@ namespace CSMWorld { ColumnId_Idle6, "Idle 6" }, { ColumnId_Idle7, "Idle 7" }, { ColumnId_Idle8, "Idle 8" }, + { ColumnId_Idle9, "Idle 9" }, { ColumnId_RegionWeather, "Weather" }, { ColumnId_WeatherName, "Type" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 3c4bff07f6..469c1eee33 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -310,14 +310,14 @@ namespace CSMWorld ColumnId_MaxAttack = 284, ColumnId_CreatureMisc = 285, - ColumnId_Idle1 = 286, - ColumnId_Idle2 = 287, - ColumnId_Idle3 = 288, - ColumnId_Idle4 = 289, - ColumnId_Idle5 = 290, - ColumnId_Idle6 = 291, - ColumnId_Idle7 = 292, - ColumnId_Idle8 = 293, + ColumnId_Idle2 = 286, + ColumnId_Idle3 = 287, + ColumnId_Idle4 = 288, + ColumnId_Idle5 = 289, + ColumnId_Idle6 = 290, + ColumnId_Idle7 = 291, + ColumnId_Idle8 = 292, + ColumnId_Idle9 = 293, ColumnId_RegionWeather = 294, ColumnId_WeatherName = 295, diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index c3af3d4673..e0d5799726 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -210,7 +210,6 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiDuration, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiWanderToD, CSMWorld::ColumnBase::Display_Integer)); - mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle1, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle2, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle3, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle4, CSMWorld::ColumnBase::Display_Integer)); @@ -218,6 +217,7 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle6, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle7, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle8, CSMWorld::ColumnBase::Display_Integer)); + mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_Idle9, CSMWorld::ColumnBase::Display_Integer)); mColumns.back().addColumn(new RefIdColumn(Columns::ColumnId_AiWanderRepeat, CSMWorld::ColumnBase::Display_Boolean)); mColumns.back().addColumn( From 231ec03ef4bcc4a27820ddbe4cc617751c093b44 Mon Sep 17 00:00:00 2001 From: Joakim Berg Date: Mon, 15 Apr 2024 17:30:11 +0000 Subject: [PATCH 397/451] swedish calendar translation --- files/data/l10n/Calendar/sv.yaml | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 files/data/l10n/Calendar/sv.yaml diff --git a/files/data/l10n/Calendar/sv.yaml b/files/data/l10n/Calendar/sv.yaml new file mode 100644 index 0000000000..f3ac2b915b --- /dev/null +++ b/files/data/l10n/Calendar/sv.yaml @@ -0,0 +1,50 @@ +# Swedish does not actually upper-case first letter on months and weekday names, so I'm doing them lower case right now. + +month1: "januari" +month2: "februari" +month3: "mars" +month4: "april" +month5: "maj" +month6: "juni" +month7: "juli" +month8: "augusti" +month9: "september" +month10: "oktober" +month11: "november" +month12: "december" + +# There are no different grammatical forms of the months in Swedish + +monthInGenitive1: "januari" +monthInGenitive2: "februari" +monthInGenitive3: "mars" +monthInGenitive4: "april" +monthInGenitive5: "maj" +monthInGenitive6: "juni" +monthInGenitive7: "juli" +monthInGenitive8: "augusti" +monthInGenitive9: "september" +monthInGenitive10: "oktober" +monthInGenitive11: "november" +monthInGenitive12: "december" + +# Standard Swedish date format: d MMMM YYYY +# Source: http://www4.sprakochfolkminnen.se/cgi-bin/srfl/visasvar.py?sok=datum&svar=26089 +# Example: "23 Februari 1337" +dateFormat: "{day} {month} {year, number, :: group-off}" + +# The Swedish week starts with monday actually, but whatever. + +weekday1: "söndag" +weekday2: "måndag" +weekday3: "tisdag" +weekday4: "onsdag" +weekday5: "torsdag" +weekday6: "fredag" +weekday7: "lördag" + +# In Swedish, as with German, we don't use AM/PM but instead a 24h clock. +# But instead of that, we could use "förmiddag" and "eftermiddag", which is basically "morning" and "afternoon" +am: "förmiddag" +pm: "eftermiddag" +day: "dag" From 9448a30caffac31f5372c03accc17e2ea61adb18 Mon Sep 17 00:00:00 2001 From: Joakim Berg Date: Mon, 15 Apr 2024 17:53:17 +0000 Subject: [PATCH 398/451] Accidental upper case in a comment --- files/data/l10n/Calendar/sv.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/l10n/Calendar/sv.yaml b/files/data/l10n/Calendar/sv.yaml index f3ac2b915b..858e586412 100644 --- a/files/data/l10n/Calendar/sv.yaml +++ b/files/data/l10n/Calendar/sv.yaml @@ -30,7 +30,7 @@ monthInGenitive12: "december" # Standard Swedish date format: d MMMM YYYY # Source: http://www4.sprakochfolkminnen.se/cgi-bin/srfl/visasvar.py?sok=datum&svar=26089 -# Example: "23 Februari 1337" +# Example: "23 februari 1337" dateFormat: "{day} {month} {year, number, :: group-off}" # The Swedish week starts with monday actually, but whatever. From f184d8f390f084a99cd2276d9ea1c6bad5ea4848 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 15 Apr 2024 00:02:12 +0200 Subject: [PATCH 399/451] Use RAII for AVIOContext, AVFormatContext, AVCodecContext and AVFrame pointers --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 200 +++++++++++-------------- apps/openmw/mwsound/ffmpeg_decoder.hpp | 35 ++++- 2 files changed, 122 insertions(+), 113 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index a6f3d0336f..9e7a2be3a8 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -1,15 +1,41 @@ #include "ffmpeg_decoder.hpp" -#include - #include +#include #include +#include #include #include namespace MWSound { + void AVIOContextDeleter::operator()(AVIOContext* ptr) const + { + if (ptr->buffer != nullptr) + av_freep(&ptr->buffer); + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100) + avio_context_free(&ptr); +#else + av_free(ptr); +#endif + } + + void AVFormatContextDeleter::operator()(AVFormatContext* ptr) const + { + avformat_close_input(&ptr); + } + + void AVCodecContextDeleter::operator()(AVCodecContext* ptr) const + { + avcodec_free_context(&ptr); + } + + void AVFrameDeleter::operator()(AVFrame* ptr) const + { + av_frame_free(&ptr); + } int FFmpeg_Decoder::readPacket(void* user_data, uint8_t* buf, int buf_size) { @@ -75,7 +101,7 @@ namespace MWSound return false; std::ptrdiff_t stream_idx = mStream - mFormatCtx->streams; - while (av_read_frame(mFormatCtx, &mPacket) >= 0) + while (av_read_frame(mFormatCtx.get(), &mPacket) >= 0) { /* Check if the packet belongs to this stream */ if (stream_idx == mPacket.stream_index) @@ -102,12 +128,12 @@ namespace MWSound do { /* Decode some data, and check for errors */ - int ret = avcodec_receive_frame(mCodecCtx, mFrame); + int ret = avcodec_receive_frame(mCodecCtx.get(), mFrame.get()); if (ret == AVERROR(EAGAIN)) { if (mPacket.size == 0 && !getNextPacket()) return false; - ret = avcodec_send_packet(mCodecCtx, &mPacket); + ret = avcodec_send_packet(mCodecCtx.get(), &mPacket); av_packet_unref(&mPacket); if (ret == 0) continue; @@ -187,137 +213,95 @@ namespace MWSound close(); mDataStream = mResourceMgr->get(fname); - if ((mFormatCtx = avformat_alloc_context()) == nullptr) + AVIOContextPtr ioCtx(avio_alloc_context(nullptr, 0, 0, this, readPacket, writePacket, seek)); + if (ioCtx == nullptr) + throw std::runtime_error("Failed to allocate AVIO context"); + + AVFormatContext* formatCtx = avformat_alloc_context(); + if (formatCtx == nullptr) throw std::runtime_error("Failed to allocate context"); - try + formatCtx->pb = ioCtx.get(); + + // avformat_open_input frees user supplied AVFormatContext on failure + if (avformat_open_input(&formatCtx, fname.c_str(), nullptr, nullptr) != 0) + throw std::runtime_error("Failed to open input"); + + AVFormatContextPtr formatCtxPtr(std::exchange(formatCtx, nullptr)); + + if (avformat_find_stream_info(formatCtxPtr.get(), nullptr) < 0) + throw std::runtime_error("Failed to find stream info"); + + AVStream** stream = nullptr; + for (size_t j = 0; j < formatCtxPtr->nb_streams; j++) { - mFormatCtx->pb = avio_alloc_context(nullptr, 0, 0, this, readPacket, writePacket, seek); - if (!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), nullptr, nullptr) != 0) + if (formatCtxPtr->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - // "Note that a user-supplied AVFormatContext will be freed on failure". - if (mFormatCtx) - { - if (mFormatCtx->pb != nullptr) - { - if (mFormatCtx->pb->buffer != nullptr) - { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = nullptr; - } - av_free(mFormatCtx->pb); - mFormatCtx->pb = nullptr; - } - avformat_free_context(mFormatCtx); - } - mFormatCtx = nullptr; - throw std::runtime_error("Failed to allocate input stream"); + stream = &formatCtxPtr->streams[j]; + break; } + } - if (avformat_find_stream_info(mFormatCtx, nullptr) < 0) - throw std::runtime_error("Failed to find stream info in " + fname); + if (stream == nullptr) + throw std::runtime_error("No audio streams"); - for (size_t j = 0; j < mFormatCtx->nb_streams; j++) - { - if (mFormatCtx->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) - { - mStream = &mFormatCtx->streams[j]; - break; - } - } - if (!mStream) - throw std::runtime_error("No audio streams in " + fname); + const AVCodec* codec = avcodec_find_decoder((*stream)->codecpar->codec_id); + if (codec == nullptr) + throw std::runtime_error("No codec found for id " + std::to_string((*stream)->codecpar->codec_id)); - const AVCodec* codec = avcodec_find_decoder((*mStream)->codecpar->codec_id); - if (!codec) - { - std::string ss = "No codec found for id " + std::to_string((*mStream)->codecpar->codec_id); - throw std::runtime_error(ss); - } + AVCodecContext* codecCtx = avcodec_alloc_context3(codec); + if (codecCtx == nullptr) + throw std::runtime_error("Failed to allocate codec context"); - AVCodecContext* avctx = avcodec_alloc_context3(codec); - avcodec_parameters_to_context(avctx, (*mStream)->codecpar); + avcodec_parameters_to_context(codecCtx, (*stream)->codecpar); // This is not needed anymore above FFMpeg version 4.0 #if LIBAVCODEC_VERSION_INT < 3805796 - av_codec_set_pkt_timebase(avctx, (*mStream)->time_base); + av_codec_set_pkt_timebase(avctx, (*stream)->time_base); #endif - mCodecCtx = avctx; - - if (avcodec_open2(mCodecCtx, codec, nullptr) < 0) - throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name); + AVCodecContextPtr codecCtxPtr(std::exchange(codecCtx, nullptr)); - mFrame = av_frame_alloc(); + if (avcodec_open2(codecCtxPtr.get(), codec, nullptr) < 0) + throw std::runtime_error(std::string("Failed to open audio codec ") + codec->long_name); - if (mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8P) - mOutputSampleFormat = AV_SAMPLE_FMT_U8; - // FIXME: Check for AL_EXT_FLOAT32 support - // else if (mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP) - // mOutputSampleFormat = AV_SAMPLE_FMT_S16; - else - mOutputSampleFormat = AV_SAMPLE_FMT_S16; + AVFramePtr frame(av_frame_alloc()); + if (frame == nullptr) + throw std::runtime_error("Failed to allocate frame"); - mOutputChannelLayout = (*mStream)->codecpar->channel_layout; - if (mOutputChannelLayout == 0) - mOutputChannelLayout = av_get_default_channel_layout(mCodecCtx->channels); + if (codecCtxPtr->sample_fmt == AV_SAMPLE_FMT_U8P) + mOutputSampleFormat = AV_SAMPLE_FMT_U8; + // FIXME: Check for AL_EXT_FLOAT32 support + // else if (codecCtxPtr->sample_fmt == AV_SAMPLE_FMT_FLT || codecCtxPtr->sample_fmt == AV_SAMPLE_FMT_FLTP) + // mOutputSampleFormat = AV_SAMPLE_FMT_S16; + else + mOutputSampleFormat = AV_SAMPLE_FMT_S16; - mCodecCtx->channel_layout = mOutputChannelLayout; - } - catch (...) - { - if (mStream) - avcodec_free_context(&mCodecCtx); - mStream = nullptr; + mOutputChannelLayout = (*stream)->codecpar->channel_layout; + if (mOutputChannelLayout == 0) + mOutputChannelLayout = av_get_default_channel_layout(codecCtxPtr->channels); - if (mFormatCtx != nullptr) - { - if (mFormatCtx->pb->buffer != nullptr) - { - av_free(mFormatCtx->pb->buffer); - mFormatCtx->pb->buffer = nullptr; - } - av_free(mFormatCtx->pb); - mFormatCtx->pb = nullptr; + codecCtxPtr->channel_layout = mOutputChannelLayout; - avformat_close_input(&mFormatCtx); - } - } + mIoCtx = std::move(ioCtx); + mFrame = std::move(frame); + mFormatCtx = std::move(formatCtxPtr); + mCodecCtx = std::move(codecCtxPtr); + mStream = stream; } void FFmpeg_Decoder::close() { - if (mStream) - avcodec_free_context(&mCodecCtx); mStream = nullptr; + mCodecCtx.reset(); av_packet_unref(&mPacket); av_freep(&mDataBuf); - av_frame_free(&mFrame); + mFrame.reset(); swr_free(&mSwr); - if (mFormatCtx) - { - if (mFormatCtx->pb != nullptr) - { - // mFormatCtx->pb->buffer must be freed by hand, - // if not, valgrind will show memleak, see: - // - // https://trac.ffmpeg.org/ticket/1357 - // - if (mFormatCtx->pb->buffer != nullptr) - { - av_freep(&mFormatCtx->pb->buffer); - } -#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100) - avio_context_free(&mFormatCtx->pb); -#else - av_freep(&mFormatCtx->pb); -#endif - } - avformat_close_input(&mFormatCtx); - } - + mFormatCtx.reset(); + mIoCtx.reset(); mDataStream.reset(); } @@ -436,10 +420,7 @@ namespace MWSound FFmpeg_Decoder::FFmpeg_Decoder(const VFS::Manager* vfs) : Sound_Decoder(vfs) - , mFormatCtx(nullptr) - , mCodecCtx(nullptr) , mStream(nullptr) - , mFrame(nullptr) , mFrameSize(0) , mFramePos(0) , mNextPts(0.0) @@ -470,5 +451,4 @@ namespace MWSound { close(); } - } diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index 9d15888fcf..ed3297403e 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -32,14 +32,43 @@ extern "C" namespace MWSound { + struct AVIOContextDeleter + { + void operator()(AVIOContext* ptr) const; + }; + + using AVIOContextPtr = std::unique_ptr; + + struct AVFormatContextDeleter + { + void operator()(AVFormatContext* ptr) const; + }; + + using AVFormatContextPtr = std::unique_ptr; + + struct AVCodecContextDeleter + { + void operator()(AVCodecContext* ptr) const; + }; + + using AVCodecContextPtr = std::unique_ptr; + + struct AVFrameDeleter + { + void operator()(AVFrame* ptr) const; + }; + + using AVFramePtr = std::unique_ptr; + class FFmpeg_Decoder final : public Sound_Decoder { - AVFormatContext* mFormatCtx; - AVCodecContext* mCodecCtx; + AVIOContextPtr mIoCtx; + AVFormatContextPtr mFormatCtx; + AVCodecContextPtr mCodecCtx; AVStream** mStream; AVPacket mPacket; - AVFrame* mFrame; + AVFramePtr mFrame; std::size_t mFrameSize; std::size_t mFramePos; From 443e341ae763cc906324e2445b52b894be87b610 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 16 Apr 2024 02:52:25 +0300 Subject: [PATCH 400/451] Generalize unsized pixel format computation --- components/sceneutil/util.cpp | 117 ++++++++++++++++++++++++++-- components/sceneutil/util.hpp | 4 +- components/shader/shadervisitor.cpp | 10 ++- components/terrain/material.cpp | 13 +++- 4 files changed, 132 insertions(+), 12 deletions(-) diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index a5629ee092..21a753df12 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -286,18 +286,125 @@ namespace SceneUtil mOperationQueue->add(operation); } - bool isRedGreenPixelFormat(GLenum format) + GLenum computeUnsizedPixelFormat(GLenum format) { switch (format) { - case GL_RG: - case GL_RG_INTEGER: + // Try compressed formats first, they're more likely to be used + + // Generic + case GL_COMPRESSED_ALPHA_ARB: + return GL_ALPHA; + case GL_COMPRESSED_INTENSITY_ARB: + return GL_INTENSITY; + case GL_COMPRESSED_LUMINANCE_ALPHA_ARB: + return GL_LUMINANCE_ALPHA; + case GL_COMPRESSED_LUMINANCE_ARB: + return GL_LUMINANCE; + case GL_COMPRESSED_RGB_ARB: + return GL_RGB; + case GL_COMPRESSED_RGBA_ARB: + return GL_RGBA; + + // S3TC + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT: + return GL_RGB; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: + return GL_RGBA; + + // RGTC + case GL_COMPRESSED_RED_RGTC1_EXT: + case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT: + return GL_RED; case GL_COMPRESSED_RED_GREEN_RGTC2_EXT: case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT: - return true; + return GL_RG; + + // PVRTC + case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG: + case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG: + return GL_RGB; + case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG: + case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG: + return GL_RGBA; + + // ETC + case GL_COMPRESSED_R11_EAC: + case GL_COMPRESSED_SIGNED_R11_EAC: + return GL_RED; + case GL_COMPRESSED_RG11_EAC: + case GL_COMPRESSED_SIGNED_RG11_EAC: + return GL_RG; + case GL_ETC1_RGB8_OES: + case GL_COMPRESSED_RGB8_ETC2: + case GL_COMPRESSED_SRGB8_ETC2: + return GL_RGB; + case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: + case GL_COMPRESSED_RGBA8_ETC2_EAC: + case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: + return GL_RGBA; + + // ASTC + case GL_COMPRESSED_RGBA_ASTC_4x4_KHR: + case GL_COMPRESSED_RGBA_ASTC_5x4_KHR: + case GL_COMPRESSED_RGBA_ASTC_5x5_KHR: + case GL_COMPRESSED_RGBA_ASTC_6x5_KHR: + case GL_COMPRESSED_RGBA_ASTC_6x6_KHR: + case GL_COMPRESSED_RGBA_ASTC_8x5_KHR: + case GL_COMPRESSED_RGBA_ASTC_8x6_KHR: + case GL_COMPRESSED_RGBA_ASTC_8x8_KHR: + case GL_COMPRESSED_RGBA_ASTC_10x5_KHR: + case GL_COMPRESSED_RGBA_ASTC_10x6_KHR: + case GL_COMPRESSED_RGBA_ASTC_10x8_KHR: + case GL_COMPRESSED_RGBA_ASTC_10x10_KHR: + case GL_COMPRESSED_RGBA_ASTC_12x10_KHR: + case GL_COMPRESSED_RGBA_ASTC_12x12_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR: + case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR: + return GL_RGBA; + + // Plug in some holes computePixelFormat has, you never know when these could come in handy + case GL_INTENSITY4: + case GL_INTENSITY8: + case GL_INTENSITY12: + case GL_INTENSITY16: + return GL_INTENSITY; + + case GL_LUMINANCE4: + case GL_LUMINANCE8: + case GL_LUMINANCE12: + case GL_LUMINANCE16: + return GL_LUMINANCE; + + case GL_LUMINANCE4_ALPHA4: + case GL_LUMINANCE6_ALPHA2: + case GL_LUMINANCE8_ALPHA8: + case GL_LUMINANCE12_ALPHA4: + case GL_LUMINANCE12_ALPHA12: + case GL_LUMINANCE16_ALPHA16: + return GL_LUMINANCE_ALPHA; } - return false; + return osg::Image::computePixelFormat(format); } } diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 0f4b82bbe0..b76f46a688 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -113,7 +113,9 @@ namespace SceneUtil osg::ref_ptr mOperationQueue; }; - bool isRedGreenPixelFormat(GLenum format); + // Compute the unsized format equivalent to the given pixel format + // Unlike osg::Image::computePixelFormat, this also covers compressed formats + GLenum computeUnsizedPixelFormat(GLenum format); } #endif diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 600e35a22a..2676ea3168 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -447,10 +447,14 @@ namespace Shader if (normalMap != nullptr && normalMap->getImage(0)) { // Special handling for red-green normal maps (e.g. BC5 or R8G8) - if (SceneUtil::isRedGreenPixelFormat(normalMap->getImage(0)->getPixelFormat())) + switch (SceneUtil::computeUnsizedPixelFormat(normalMap->getImage(0)->getPixelFormat())) { - mRequirements.back().mReconstructNormalZ = true; - mRequirements.back().mNormalHeight = false; + case GL_RG: + case GL_RG_INTEGER: + { + mRequirements.back().mReconstructNormalZ = true; + mRequirements.back().mNormalHeight = false; + } } } diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 10dbeb9838..09d2680acd 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -282,10 +282,17 @@ namespace Terrain // Special handling for red-green normal maps (e.g. BC5 or R8G8). const osg::Image* image = it->mNormalMap->getImage(0); - if (image && SceneUtil::isRedGreenPixelFormat(image->getPixelFormat())) + if (image) { - reconstructNormalZ = true; - parallax = false; + switch (SceneUtil::computeUnsizedPixelFormat(image->getPixelFormat())) + { + case GL_RG: + case GL_RG_INTEGER: + { + reconstructNormalZ = true; + parallax = false; + } + } } } From a7021bf9cc6cfc9111505e95ebb83d2921d2f13e Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 16 Apr 2024 01:10:39 +0100 Subject: [PATCH 401/451] Clear std stream errors when reopening Prior errors are no longer relevant. Shouldn't make a difference unless you've tried printing something before the streams were set up. --- components/debug/debugging.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index bfde558c85..0699d0912b 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -73,16 +73,19 @@ namespace Debug { _wfreopen(L"CON", L"r", stdin); freopen("CON", "r", stdin); + std::cin.clear(); } if (!outRedirected) { _wfreopen(L"CON", L"w", stdout); freopen("CON", "w", stdout); + std::cout.clear(); } if (!errRedirected) { _wfreopen(L"CON", L"w", stderr); freopen("CON", "w", stderr); + std::cerr.clear(); } return true; From 61364c874f09f61870b66a6414496f741a2030ac Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 16 Apr 2024 01:14:20 +0100 Subject: [PATCH 402/451] Warn future me off wasting their time again --- components/debug/debugging.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index 0699d0912b..e6caf2b09d 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -61,6 +61,10 @@ namespace Debug bool outRedirected = isRedirected(STD_OUTPUT_HANDLE); bool errRedirected = isRedirected(STD_ERROR_HANDLE); + // Note: Do not spend three days reinvestigating this PowerShell bug thinking its our bug. + // https://gitlab.com/OpenMW/openmw/-/merge_requests/408#note_447467393 + // The handles look valid, but GetFinalPathNameByHandleA can't tell what files they go to and writing to them doesn't work. + if (AttachConsole(ATTACH_PARENT_PROCESS)) { fflush(stdout); From d8f19c6e7b8fac335cd42b599bb68cab82b75159 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 16 Apr 2024 03:29:47 +0300 Subject: [PATCH 403/451] Changelog (two-channel normal maps, #7932) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aeb030bda5..8f2ee0eb63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -225,6 +225,7 @@ Feature #7875: Disable MyGUI windows snapping Feature #7914: Do not allow to move GUI windows out of screen Feature #7923: Don't show non-existent higher ranks for factions with fewer than 9 ranks + Feature #7932: Support two-channel normal maps Task #5896: Do not use deprecated MyGUI properties Task #6085: Replace boost::filesystem with std::filesystem Task #6149: Dehardcode Lua API_REVISION From 83e3718bed550faefca66080eb66fb227fcd3f0a Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 16 Apr 2024 13:14:36 +0100 Subject: [PATCH 404/451] . c l a n g - f o r m a t --- components/debug/debugging.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/debug/debugging.cpp b/components/debug/debugging.cpp index e6caf2b09d..67e7ecaaf3 100644 --- a/components/debug/debugging.cpp +++ b/components/debug/debugging.cpp @@ -63,7 +63,8 @@ namespace Debug // Note: Do not spend three days reinvestigating this PowerShell bug thinking its our bug. // https://gitlab.com/OpenMW/openmw/-/merge_requests/408#note_447467393 - // The handles look valid, but GetFinalPathNameByHandleA can't tell what files they go to and writing to them doesn't work. + // The handles look valid, but GetFinalPathNameByHandleA can't tell what files they go to and writing to them + // doesn't work. if (AttachConsole(ATTACH_PARENT_PROCESS)) { From d06e8e2c24878dfecd1e74bca4b5ecc54c33b7ba Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 9 Apr 2024 12:24:19 +0400 Subject: [PATCH 405/451] Use Qt6 on Windows by default --- .gitlab-ci.yml | 4 ++-- CI/before_script.msvc.sh | 24 ++++++++++++++++++------ apps/opencs/CMakeLists.txt | 1 + files/windows/openmw-cs.exe.manifest | 9 +++++++++ 4 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 files/windows/openmw-cs.exe.manifest diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 06c7e63cb0..feaccc2540 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -606,7 +606,7 @@ macOS14_Xcode15_arm64: - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: ninja-v8 + key: ninja-v9 paths: - ccache - deps @@ -722,7 +722,7 @@ macOS14_Xcode15_arm64: - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: msbuild-v8 + key: msbuild-v9 paths: - deps - MSVC2019_64/deps/Qt diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 3919507bd1..b501c0162b 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -563,7 +563,7 @@ ICU_VER="70_1" LUAJIT_VER="v2.1.0-beta3-452-g7a0cf5fd" LZ4_VER="1.9.2" OPENAL_VER="1.23.0" -QT_VER="5.15.2" +QT_VER="6.6.2" OSG_ARCHIVE_NAME="OSGoS 3.6.5" OSG_ARCHIVE="OSGoS-3.6.5-123-g68c5c573d-msvc${OSG_MSVC_YEAR}-win${BITS}" @@ -894,7 +894,7 @@ printf "Qt ${QT_VER}... " printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then pushd "$DEPS" > /dev/null - AQT_VERSION="v3.1.7" + AQT_VERSION="v3.1.12" if ! [ -f "aqt_x64-${AQT_VERSION}.exe" ]; then download "aqt ${AQT_VERSION}"\ "https://github.com/miurahr/aqtinstall/releases/download/${AQT_VERSION}/aqt_x64.exe" \ @@ -915,6 +915,9 @@ printf "Qt ${QT_VER}... " echo Done. fi + QT_MAJOR_VER=$(echo "${QT_VER}" | awk -F '[.]' '{printf "%d", $1}') + QT_MINOR_VER=$(echo "${QT_VER}" | awk -F '[.]' '{printf "%d", $2}') + cd $QT_SDK for CONFIGURATION in ${CONFIGURATIONS[@]}; do if [ $CONFIGURATION == "Debug" ]; then @@ -922,13 +925,22 @@ printf "Qt ${QT_VER}... " else DLLSUFFIX="" fi - if [ "${QT_VER:0:1}" -eq "6" ]; then - add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets,Svg}${DLLSUFFIX}.dll + + if [ "${QT_MAJOR_VER}" -eq 6 ]; then + add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_MAJOR_VER}"{Core,Gui,Network,OpenGL,OpenGLWidgets,Widgets,Svg}${DLLSUFFIX}.dll + + # Since Qt 6.7.0 plugin is called "qmodernwindowsstyle" + if [ "${QT_MINOR_VER}" -ge 7 ]; then + add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qmodernwindowsstyle${DLLSUFFIX}.dll" + else + add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" + fi else - add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_VER:0:1}"{Core,Gui,Network,OpenGL,Widgets,Svg}${DLLSUFFIX}.dll + add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt${QT_MAJOR_VER}"{Core,Gui,Network,OpenGL,Widgets,Svg}${DLLSUFFIX}.dll + add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" fi + add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll" - add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll" add_qt_image_dlls $CONFIGURATION "$(pwd)/plugins/imageformats/qsvg${DLLSUFFIX}.dll" done echo Done. diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 3353a4586f..d6af64b33b 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -257,6 +257,7 @@ endif() if (WIN32) target_link_libraries(openmw-cs-lib ${Boost_LOCALE_LIBRARY}) + target_sources(openmw-cs PRIVATE ${CMAKE_SOURCE_DIR}/files/windows/openmw-cs.exe.manifest) endif() if (WIN32 AND BUILD_OPENCS) diff --git a/files/windows/openmw-cs.exe.manifest b/files/windows/openmw-cs.exe.manifest new file mode 100644 index 0000000000..3107f64706 --- /dev/null +++ b/files/windows/openmw-cs.exe.manifest @@ -0,0 +1,9 @@ + + + + + true + PerMonitorV2 + + + \ No newline at end of file From 118114c4988380ea7cef1c00878846ad8feb6d79 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 17 Apr 2024 09:04:17 +0400 Subject: [PATCH 406/451] Add misisng empty line --- CI/before_script.msvc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index b501c0162b..c2d91ccfd3 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -1135,7 +1135,7 @@ fi echo " $(basename $DLL)" cp "$DLL" "${DLL_PREFIX}styles" done - + echo echo "- Qt Image Format DLLs..." mkdir -p ${DLL_PREFIX}imageformats for DLL in ${QT_IMAGEFORMATS[$CONFIGURATION]}; do From 2653b76db9753fc4eabe68422e8a09182cedfc84 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Wed, 17 Apr 2024 08:16:48 +0100 Subject: [PATCH 407/451] getTranslation/getTransformForNode refactor, unit tests --- apps/openmw_test_suite/CMakeLists.txt | 2 + .../sceneutil/osgacontroller.cpp | 129 ++++++++++++++++++ components/sceneutil/osgacontroller.cpp | 49 +------ 3 files changed, 137 insertions(+), 43 deletions(-) create mode 100644 apps/openmw_test_suite/sceneutil/osgacontroller.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index b91940cd77..db6ecb816a 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -100,6 +100,8 @@ file(GLOB UNITTEST_SRC_FILES resource/testobjectcache.cpp vfs/testpathutil.cpp + + sceneutil/osgacontroller.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/sceneutil/osgacontroller.cpp b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp new file mode 100644 index 0000000000..2ab6aeb37e --- /dev/null +++ b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp @@ -0,0 +1,129 @@ +#include + +#include +#include + +#include +#include + +namespace +{ + using namespace SceneUtil; + + static const std::string ROOT_BONE_NAME = "bip01"; + + // Creates a merged anim track with a single root channel with two start/end matrix transforms + osg::ref_ptr createMergedAnimationTrack(std::string name, osg::Matrixf startTransform, + osg::Matrixf endTransform, float startTime = 0.0f, float endTime = 1.0f) + { + osg::ref_ptr mergedAnimationTrack = new Resource::Animation; + mergedAnimationTrack->setName(name); + + osgAnimation::MatrixKeyframeContainer* cbCntr = new osgAnimation::MatrixKeyframeContainer; + cbCntr->push_back(osgAnimation::MatrixKeyframe(startTime, startTransform)); + cbCntr->push_back(osgAnimation::MatrixKeyframe(endTime, endTransform)); + + osg::ref_ptr rootChannel = new osgAnimation::MatrixLinearChannel; + rootChannel->setName("transform"); + rootChannel->setTargetName(ROOT_BONE_NAME); + rootChannel->getOrCreateSampler()->setKeyframeContainer(cbCntr); + mergedAnimationTrack->addChannel(rootChannel); + return mergedAnimationTrack; + } + + TEST(OsgAnimationControllerTest, getTranslationShouldReturnSampledChannelTranslationForBip01) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this + emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform2 = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + endTransform2.setTrans(2.0f, 2.0f, 2.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); + + // should be halfway between 0,0,0 and 1,1,1 + osg::Vec3f translation = controller.getTranslation(0.5f); + EXPECT_EQ(translation, osg::Vec3f(0.5f, 0.5f, 0.5f)); + } + + TEST(OsgAnimationControllerTest, getTranslationShouldReturnZeroVectorIfNotFound) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + + // Has no emulated animation at time so will return 0,0,0 + osg::Vec3f translation = controller.getTranslation(100.0f); + EXPECT_EQ(translation, osg::Vec3f(0.0f, 0.0f, 0.0f)); + } + + TEST(OsgAnimationControllerTest, getTranslationShouldReturnZeroVectorIfNoMergedTracks) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + // Has no merged tracks so will return 0,0,0 + osg::Vec3f translation = controller.getTranslation(0.5); + EXPECT_EQ(translation, osg::Vec3f(0.0f, 0.0f, 0.0f)); + } + + TEST(OsgAnimationControllerTest, getTransformShouldReturnIdentityIfNotFound) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + + // Has no emulated animation at time so will return identity + EXPECT_EQ(controller.getTransformForNode(100.0f, ROOT_BONE_NAME), osg::Matrixf::identity()); + + // Has no bone animation at time so will return identity + EXPECT_EQ(controller.getTransformForNode(0.5f, "wrongbone"), osg::Matrixf::identity()); + } + + TEST(OsgAnimationControllerTest, getTransformShouldReturnSampledAnimMatrixAtTime) + { + std::vector emulatedAnimations; + emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this + emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this + + OsgAnimationController controller; + controller.setEmulatedAnimations(emulatedAnimations); + + osg::Matrixf startTransform = osg::Matrixf::identity(); + osg::Matrixf endTransform = osg::Matrixf::identity(); + endTransform.setTrans(1.0f, 1.0f, 1.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); + osg::Matrixf endTransform2 = osg::Matrixf::identity(); + endTransform2.setTrans(2.0f, 2.0f, 2.0f); + controller.addMergedAnimationTrack(createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); + + EXPECT_EQ(controller.getTransformForNode(0.0f, ROOT_BONE_NAME), startTransform); // start of test1 + EXPECT_EQ(controller.getTransformForNode(1.0f, ROOT_BONE_NAME), endTransform); // end of test1 + EXPECT_EQ(controller.getTransformForNode(1.1f, ROOT_BONE_NAME), endTransform); // start of test2 + EXPECT_EQ(controller.getTransformForNode(2.0f, ROOT_BONE_NAME), endTransform2); // end of test2 + } +} diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 5a3ae60293..d3268fef36 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -91,49 +91,6 @@ namespace SceneUtil } } - osg::Vec3f OsgAnimationController::getTranslation(float time) const - { - osg::Vec3f translationValue; - std::string animationName; - float newTime = time; - - // Find the correct animation based on time - for (const EmulatedAnimation& emulatedAnimation : mEmulatedAnimations) - { - if (time >= emulatedAnimation.mStartTime && time <= emulatedAnimation.mStopTime) - { - newTime = time - emulatedAnimation.mStartTime; - animationName = emulatedAnimation.mName; - } - } - - // Find the root transform track in animation - for (const auto& mergedAnimationTrack : mMergedAnimationTracks) - { - if (mergedAnimationTrack->getName() != animationName) - continue; - - const osgAnimation::ChannelList& channels = mergedAnimationTrack->getChannels(); - - for (const auto& channel : channels) - { - if (channel->getTargetName() != "bip01" || channel->getName() != "transform") - continue; - - if (osgAnimation::MatrixLinearSampler* templateSampler - = dynamic_cast(channel->getSampler())) - { - osg::Matrixf matrix; - templateSampler->getValueAt(newTime, matrix); - translationValue = matrix.getTrans(); - return osg::Vec3f(translationValue[0], translationValue[1], translationValue[2]); - } - } - } - - return osg::Vec3f(); - } - osg::Matrixf OsgAnimationController::getTransformForNode(float time, const std::string_view name) const { std::string animationName; @@ -146,6 +103,7 @@ namespace SceneUtil { newTime = time - emulatedAnimation.mStartTime; animationName = emulatedAnimation.mName; + break; } } @@ -175,6 +133,11 @@ namespace SceneUtil return osg::Matrixf::identity(); } + osg::Vec3f OsgAnimationController::getTranslation(float time) const + { + return getTransformForNode(time, "bip01").getTrans(); + } + void OsgAnimationController::update(float time, const std::string& animationName) { for (const auto& mergedAnimationTrack : mMergedAnimationTracks) From d09f32d9e4362a4d47c2fac4d32e9aef62ac0e15 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Wed, 17 Apr 2024 08:19:49 +0100 Subject: [PATCH 408/451] Yes sir clang --- apps/openmw_test_suite/sceneutil/osgacontroller.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/openmw_test_suite/sceneutil/osgacontroller.cpp b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp index 2ab6aeb37e..309de4a878 100644 --- a/apps/openmw_test_suite/sceneutil/osgacontroller.cpp +++ b/apps/openmw_test_suite/sceneutil/osgacontroller.cpp @@ -1,7 +1,7 @@ #include -#include #include +#include #include #include @@ -36,7 +36,7 @@ namespace std::vector emulatedAnimations; emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this - + OsgAnimationController controller; controller.setEmulatedAnimations(emulatedAnimations); @@ -46,7 +46,8 @@ namespace endTransform.setTrans(1.0f, 1.0f, 1.0f); controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); endTransform2.setTrans(2.0f, 2.0f, 2.0f); - controller.addMergedAnimationTrack(createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); + controller.addMergedAnimationTrack( + createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); // should be halfway between 0,0,0 and 1,1,1 osg::Vec3f translation = controller.getTranslation(0.5f); @@ -109,7 +110,7 @@ namespace std::vector emulatedAnimations; emulatedAnimations.push_back({ 0.0f, 1.0f, "test1" }); // should sample this emulatedAnimations.push_back({ 1.1f, 2.0f, "test2" }); // should ignore this - + OsgAnimationController controller; controller.setEmulatedAnimations(emulatedAnimations); @@ -119,7 +120,8 @@ namespace controller.addMergedAnimationTrack(createMergedAnimationTrack("test1", startTransform, endTransform)); osg::Matrixf endTransform2 = osg::Matrixf::identity(); endTransform2.setTrans(2.0f, 2.0f, 2.0f); - controller.addMergedAnimationTrack(createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); + controller.addMergedAnimationTrack( + createMergedAnimationTrack("test2", endTransform, endTransform2, 0.1f, 0.9f)); EXPECT_EQ(controller.getTransformForNode(0.0f, ROOT_BONE_NAME), startTransform); // start of test1 EXPECT_EQ(controller.getTransformForNode(1.0f, ROOT_BONE_NAME), endTransform); // end of test1 From 537964f8d8c89bc5434fe2400699963018b12803 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 11:23:21 +0300 Subject: [PATCH 409/451] Reconstruct normal Z *properly* --- files/shaders/compatibility/bs/default.frag | 5 +++-- files/shaders/compatibility/groundcover.frag | 5 +++-- files/shaders/compatibility/objects.frag | 5 +++-- files/shaders/compatibility/terrain.frag | 5 +++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index d2c8de0b22..3c665752d1 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -77,10 +77,11 @@ void main() vec3 specularColor = getSpecularColor().xyz; #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); + vec3 normal = normalTex.xyz * 2.0 - 1.0; #if @reconstructNormalZ - normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); #endif - vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); + vec3 viewNormal = normalToView(normal); specularColor *= normalTex.a; #else vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); diff --git a/files/shaders/compatibility/groundcover.frag b/files/shaders/compatibility/groundcover.frag index aab37d465d..96a79c8793 100644 --- a/files/shaders/compatibility/groundcover.frag +++ b/files/shaders/compatibility/groundcover.frag @@ -60,10 +60,11 @@ void main() #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); + vec3 normal = normalTex.xyz * 2.0 - 1.0; #if @reconstructNormalZ - normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); #endif - vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); + vec3 viewNormal = normalToView(normal); #else vec3 viewNormal = normalToView(normalize(passNormal)); #endif diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index eb5b79a0c2..7f163580cc 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -168,10 +168,11 @@ vec2 screenCoords = gl_FragCoord.xy / screenRes; #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV + offset); + vec3 normal = normalTex.xyz * 2.0 - 1.0; #if @reconstructNormalZ - normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); #endif - vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); + vec3 viewNormal = normalToView(normal); #else vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index f45f1f024e..9d89217f35 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -64,10 +64,11 @@ void main() #if @normalMap vec4 normalTex = texture2D(normalMap, adjustedUV); + vec3 normal = normalTex.xyz * 2.0 - 1.0; #if @reconstructNormalZ - normalTex.z = sqrt(1.0 - dot(normalTex.xy, normalTex.xy)); + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); #endif - vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); + vec3 viewNormal = normalToView(normal); #else vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif From b7aa3b9f47a1c28413bf30a67da7428701344d39 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 19 Apr 2024 07:48:26 +0100 Subject: [PATCH 410/451] Remove rename from RenameBonesVisitor, rename to RenameAnimCallbacksVisitor --- components/resource/scenemanager.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 4bdd620819..ab3f92f10d 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -383,18 +383,16 @@ namespace Resource } } - class RenameBonesVisitor : public osg::NodeVisitor + class RenameAnimCallbacksVisitor : public osg::NodeVisitor { public: - RenameBonesVisitor() + RenameAnimCallbacksVisitor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) { } void apply(osg::MatrixTransform& node) override { - node.setName(Misc::StringUtils::underscoresToSpaces(node.getName())); - // osgAnimation update callback name must match bone name/channel targets osg::Callback* cb = node.getUpdateCallback(); while (cb) @@ -687,7 +685,7 @@ namespace Resource { // Collada bones may have underscores in place of spaces due to a collada limitation // we should rename the bones and update callbacks here at load time - Resource::RenameBonesVisitor renameBoneVisitor; + Resource::RenameAnimCallbacksVisitor renameBoneVisitor; node->accept(renameBoneVisitor); if (osg::Group* group = dynamic_cast(node)) From 82931059fd82c3e396f757081466fcbb03c78051 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 19 Apr 2024 22:34:40 +0200 Subject: [PATCH 411/451] Make NormalizedView constructor from const char* explicit --- apps/openmw/mwgui/mainmenu.cpp | 5 ++++- apps/openmw/mwgui/windowmanagerimp.cpp | 3 ++- apps/openmw_test_suite/misc/test_resourcehelpers.cpp | 9 ++++++--- apps/openmw_test_suite/vfs/testpathutil.cpp | 2 +- components/resource/scenemanager.cpp | 3 ++- components/vfs/pathutil.hpp | 2 +- 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index be3700342a..53f791fdac 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include "../mwbase/environment.hpp" @@ -37,7 +38,9 @@ namespace MWGui getWidget(mVersionText, "VersionText"); mVersionText->setCaption(versionDescription); - mHasAnimatedMenu = mVFS->exists("video/menu_background.bik"); + constexpr VFS::Path::NormalizedView menuBackgroundVideo("video/menu_background.bik"); + + mHasAnimatedMenu = mVFS->exists(menuBackgroundVideo); updateMenu(); } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 61a8b3361c..3212e8f02b 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -356,7 +356,8 @@ namespace MWGui mWindows.push_back(std::move(console)); trackWindow(mConsole, makeConsoleWindowSettingValues()); - bool questList = mResourceSystem->getVFS()->exists("textures/tx_menubook_options_over.dds"); + constexpr VFS::Path::NormalizedView menubookOptionsOverTexture("textures/tx_menubook_options_over.dds"); + const bool questList = mResourceSystem->getVFS()->exists(menubookOptionsOverTexture); auto journal = JournalWindow::create(JournalViewModel::create(), questList, mEncoding); mGuiModeStates[GM_Journal] = GuiModeState(journal.get()); mWindows.push_back(std::move(journal)); diff --git a/apps/openmw_test_suite/misc/test_resourcehelpers.cpp b/apps/openmw_test_suite/misc/test_resourcehelpers.cpp index 5290630394..48edb72578 100644 --- a/apps/openmw_test_suite/misc/test_resourcehelpers.cpp +++ b/apps/openmw_test_suite/misc/test_resourcehelpers.cpp @@ -8,19 +8,22 @@ namespace TEST(CorrectSoundPath, wav_files_not_overridden_with_mp3_in_vfs_are_not_corrected) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { "sound/bar.wav", nullptr } }); - EXPECT_EQ(correctSoundPath("sound/bar.wav", *mVFS), "sound/bar.wav"); + constexpr VFS::Path::NormalizedView path("sound/bar.wav"); + EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/bar.wav"); } TEST(CorrectSoundPath, wav_files_overridden_with_mp3_in_vfs_are_corrected) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({ { "sound/foo.mp3", nullptr } }); - EXPECT_EQ(correctSoundPath("sound/foo.wav", *mVFS), "sound/foo.mp3"); + constexpr VFS::Path::NormalizedView path("sound/foo.wav"); + EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/foo.mp3"); } TEST(CorrectSoundPath, corrected_path_does_not_check_existence_in_vfs) { std::unique_ptr mVFS = TestingOpenMW::createTestVFS({}); - EXPECT_EQ(correctSoundPath("sound/foo.wav", *mVFS), "sound/foo.mp3"); + constexpr VFS::Path::NormalizedView path("sound/foo.wav"); + EXPECT_EQ(correctSoundPath(path, *mVFS), "sound/foo.mp3"); } namespace diff --git a/apps/openmw_test_suite/vfs/testpathutil.cpp b/apps/openmw_test_suite/vfs/testpathutil.cpp index 6eb84f97d5..3819f9905a 100644 --- a/apps/openmw_test_suite/vfs/testpathutil.cpp +++ b/apps/openmw_test_suite/vfs/testpathutil.cpp @@ -39,7 +39,7 @@ namespace VFS::Path TEST(NormalizedTest, shouldSupportConstructorFromNormalizedView) { - const NormalizedView view = "foo/bar/baz"; + const NormalizedView view("foo/bar/baz"); const Normalized value(view); EXPECT_EQ(value.view(), "foo/bar/baz"); } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index e4d0b4363d..6aeae80af6 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -865,7 +865,8 @@ namespace Resource << ", using embedded marker_error instead"; } Files::IMemStream file(ErrorMarker::sValue.data(), ErrorMarker::sValue.size()); - return loadNonNif("error_marker.osgt", file, mImageManager); + constexpr VFS::Path::NormalizedView errorMarker("error_marker.osgt"); + return loadNonNif(errorMarker, file, mImageManager); } osg::ref_ptr SceneManager::cloneErrorMarker() diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 07e73acfa9..1696e656ad 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -79,7 +79,7 @@ namespace VFS::Path public: constexpr NormalizedView() noexcept = default; - constexpr NormalizedView(const char* value) + constexpr explicit NormalizedView(const char* value) : mValue(value) { if (!isNormalized(mValue)) From 38b005cda6a5d7de526a676777707348dc603aff Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 12 Mar 2024 00:25:48 +0100 Subject: [PATCH 412/451] Use normalized path to store playlist music files --- apps/openmw/mwsound/soundmanagerimp.cpp | 17 +++++++++++------ apps/openmw/mwsound/soundmanagerimp.hpp | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 56224b4dcb..6ad1359c0d 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -300,13 +300,16 @@ namespace MWSound void SoundManager::startRandomTitle() { - const std::vector& filelist = mMusicFiles[mCurrentPlaylist]; - if (filelist.empty()) + const auto playlist = mMusicFiles.find(mCurrentPlaylist); + + if (playlist == mMusicFiles.end() || playlist->second.empty()) { advanceMusic(std::string()); return; } + const std::vector& filelist = playlist->second; + auto& tracklist = mMusicToPlay[mCurrentPlaylist]; // Do a Fisher-Yates shuffle @@ -359,18 +362,20 @@ namespace MWSound if (mCurrentPlaylist == playlist) return; - if (mMusicFiles.find(playlist) == mMusicFiles.end()) + auto it = mMusicFiles.find(playlist); + + if (it == mMusicFiles.end()) { - std::vector filelist; + std::vector filelist; auto playlistPath = Misc::ResourceHelpers::correctMusicPath(playlist) + '/'; for (const auto& name : mVFS->getRecursiveDirectoryIterator(playlistPath)) filelist.push_back(name); - mMusicFiles[playlist] = std::move(filelist); + it = mMusicFiles.emplace_hint(it, playlist, std::move(filelist)); } // No Battle music? Use Explore playlist - if (playlist == "Battle" && mMusicFiles[playlist].empty()) + if (playlist == "Battle" && it->second.empty()) { playPlaylist("Explore"); return; diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 75b1193118..bd9743c528 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -52,7 +52,7 @@ namespace MWSound std::unique_ptr mOutput; // Caches available music tracks by - std::unordered_map> mMusicFiles; + std::unordered_map> mMusicFiles; std::unordered_map> mMusicToPlay; // A list with music files not yet played std::string mLastPlayedMusic; // The music file that was last played From e11a5a43526778468376e9a813c2ba86ca9a2363 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 12 Mar 2024 00:40:03 +0100 Subject: [PATCH 413/451] Use normalized path for SoundManager::playPlaylist --- apps/niftest/niftest.cpp | 2 +- apps/opencs/model/world/resources.cpp | 2 +- apps/openmw/mwbase/soundmanager.hpp | 2 +- apps/openmw/mwgui/loadingscreen.cpp | 3 ++- apps/openmw/mwgui/settingswindow.cpp | 3 ++- .../mwmechanics/mechanicsmanagerimp.cpp | 6 +++-- apps/openmw/mwsound/constants.hpp | 12 ++++++++++ apps/openmw/mwsound/soundmanagerimp.cpp | 23 ++++++++++--------- apps/openmw/mwsound/soundmanagerimp.hpp | 8 ++++--- apps/openmw/mwworld/worldimp.cpp | 4 +++- components/misc/resourcehelpers.cpp | 6 +++-- components/misc/resourcehelpers.hpp | 2 +- components/vfs/manager.cpp | 17 ++++++++++++++ components/vfs/manager.hpp | 4 ++++ components/vfs/pathutil.hpp | 22 ++++++++++++++++++ 15 files changed, 91 insertions(+), 25 deletions(-) create mode 100644 apps/openmw/mwsound/constants.hpp diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index b37d85d739..5dbe99c356 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -95,7 +95,7 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat vfs.addArchive(std::move(archive)); vfs.buildIndex(); - for (const auto& name : vfs.getRecursiveDirectoryIterator("")) + for (const auto& name : vfs.getRecursiveDirectoryIterator()) { if (isNIF(name.value())) { diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 9957af3d66..7082575c64 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -28,7 +28,7 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char* const* e size_t baseSize = mBaseDirectory.size(); - for (const auto& filepath : vfs->getRecursiveDirectoryIterator("")) + for (const auto& filepath : vfs->getRecursiveDirectoryIterator()) { const std::string_view view = filepath.view(); if (view.size() < baseSize + 1 || !view.starts_with(mBaseDirectory) || view[baseSize] != '/') diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 05b925f87d..a55d696224 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -126,7 +126,7 @@ namespace MWBase virtual bool isMusicPlaying() = 0; ///< Returns true if music is playing - virtual void playPlaylist(const std::string& playlist) = 0; + virtual void playPlaylist(VFS::Path::NormalizedView playlist) = 0; ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist /// Title music playlist is predefined diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 8ba2bb8312..3a9aba7828 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -67,7 +67,8 @@ namespace MWGui != supported_extensions.end(); }; - for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator("Splash/")) + constexpr VFS::Path::NormalizedView splash("splash/"); + for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(splash)) { if (isSupportedExtension(Misc::getFileExtension(name))) mSplashScreens.push_back(name); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 396d0b18a3..075a338592 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -402,7 +402,8 @@ namespace MWGui std::vector availableLanguages; const VFS::Manager* vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - for (const auto& path : vfs->getRecursiveDirectoryIterator("l10n/")) + constexpr VFS::Path::NormalizedView l10n("l10n/"); + for (const auto& path : vfs->getRecursiveDirectoryIterator(l10n)) { if (Misc::getFileExtension(path) == "yaml") { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 47fba46c75..048476c6ef 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -29,6 +29,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwsound/constants.hpp" + #include "actor.hpp" #include "actors.hpp" #include "actorutil.hpp" @@ -1678,12 +1680,12 @@ namespace MWMechanics if (mMusicType != MWSound::MusicType::Explore && !hasHostiles && !(player.getClass().getCreatureStats(player).isDead() && musicPlaying)) { - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); + MWBase::Environment::get().getSoundManager()->playPlaylist(MWSound::explorePlaylist); mMusicType = MWSound::MusicType::Explore; } else if (mMusicType != MWSound::MusicType::Battle && hasHostiles) { - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Battle")); + MWBase::Environment::get().getSoundManager()->playPlaylist(MWSound::battlePlaylist); mMusicType = MWSound::MusicType::Battle; } } diff --git a/apps/openmw/mwsound/constants.hpp b/apps/openmw/mwsound/constants.hpp new file mode 100644 index 0000000000..5022b142f9 --- /dev/null +++ b/apps/openmw/mwsound/constants.hpp @@ -0,0 +1,12 @@ +#ifndef OPENMW_APPS_OPENMW_MWSOUND_CONSTANTS_H +#define OPENMW_APPS_OPENMW_MWSOUND_CONSTANTS_H + +#include + +namespace MWSound +{ + constexpr VFS::Path::NormalizedView battlePlaylist("battle"); + constexpr VFS::Path::NormalizedView explorePlaylist("explore"); +} + +#endif diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 6ad1359c0d..4c3f227ecd 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -26,14 +26,14 @@ #include "../mwmechanics/actorutil.hpp" +#include "constants.hpp" +#include "ffmpeg_decoder.hpp" +#include "openal_output.hpp" #include "sound.hpp" #include "sound_buffer.hpp" #include "sound_decoder.hpp" #include "sound_output.hpp" -#include "ffmpeg_decoder.hpp" -#include "openal_output.hpp" - namespace MWSound { namespace @@ -352,12 +352,12 @@ namespace MWSound mechanicsManager->setMusicType(type); advanceMusic(normalizedName, fade); if (type == MWSound::MusicType::Battle) - mCurrentPlaylist = "Battle"; + mCurrentPlaylist = battlePlaylist; else if (type == MWSound::MusicType::Explore) - mCurrentPlaylist = "Explore"; + mCurrentPlaylist = explorePlaylist; } - void SoundManager::playPlaylist(const std::string& playlist) + void SoundManager::playPlaylist(VFS::Path::NormalizedView playlist) { if (mCurrentPlaylist == playlist) return; @@ -367,17 +367,18 @@ namespace MWSound if (it == mMusicFiles.end()) { std::vector filelist; - auto playlistPath = Misc::ResourceHelpers::correctMusicPath(playlist) + '/'; - for (const auto& name : mVFS->getRecursiveDirectoryIterator(playlistPath)) + constexpr VFS::Path::NormalizedView music("music"); + const VFS::Path::Normalized playlistPath = music / playlist / VFS::Path::NormalizedView(); + for (const auto& name : mVFS->getRecursiveDirectoryIterator(VFS::Path::NormalizedView(playlistPath))) filelist.push_back(name); it = mMusicFiles.emplace_hint(it, playlist, std::move(filelist)); } // No Battle music? Use Explore playlist - if (playlist == "Battle" && it->second.empty()) + if (playlist == battlePlaylist && it->second.empty()) { - playPlaylist("Explore"); + playPlaylist(explorePlaylist); return; } @@ -1024,7 +1025,7 @@ namespace MWSound mTimePassed = 0.0f; // Make sure music is still playing - if (!isMusicPlaying() && !mCurrentPlaylist.empty()) + if (!isMusicPlaying() && !mCurrentPlaylist.value().empty()) startRandomTitle(); Environment env = Env_Normal; diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index bd9743c528..7fc7c143d5 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include "../mwbase/soundmanager.hpp" @@ -52,7 +53,8 @@ namespace MWSound std::unique_ptr mOutput; // Caches available music tracks by - std::unordered_map> mMusicFiles; + std::unordered_map, VFS::Path::Hash, std::equal_to<>> + mMusicFiles; std::unordered_map> mMusicToPlay; // A list with music files not yet played std::string mLastPlayedMusic; // The music file that was last played @@ -90,7 +92,7 @@ namespace MWSound TrackList mActiveTracks; StreamPtr mMusic; - std::string mCurrentPlaylist; + VFS::Path::Normalized mCurrentPlaylist; bool mListenerUnderwater; osg::Vec3f mListenerPos; @@ -183,7 +185,7 @@ namespace MWSound bool isMusicPlaying() override; ///< Returns true if music is playing - void playPlaylist(const std::string& playlist) override; + void playPlaylist(VFS::Path::NormalizedView playlist) override; ///< Start playing music from the selected folder /// \param name of the folder that contains the playlist /// Title music playlist is predefined diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 74335a1534..c72a9993d6 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -90,6 +90,8 @@ #include "../mwphysics/object.hpp" #include "../mwphysics/physicssystem.hpp" +#include "../mwsound/constants.hpp" + #include "actionteleport.hpp" #include "cellstore.hpp" #include "containerstore.hpp" @@ -394,7 +396,7 @@ namespace MWWorld { // Make sure that we do not continue to play a Title music after a new game video. MWBase::Environment::get().getSoundManager()->stopMusic(); - MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); + MWBase::Environment::get().getSoundManager()->playPlaylist(MWSound::explorePlaylist); MWBase::Environment::get().getWindowManager()->playVideo(video, true); } } diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 1d5b57bfd9..1eb3800012 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -186,9 +186,11 @@ VFS::Path::Normalized Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normali return prefix / resPath; } -std::string Misc::ResourceHelpers::correctMusicPath(const std::string& resPath) +std::string Misc::ResourceHelpers::correctMusicPath(std::string_view resPath) { - return "music\\" + resPath; + std::string result("music/"); + result += resPath; + return result; } std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath) diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index cda99d928d..4e747c54ee 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -42,7 +42,7 @@ namespace Misc VFS::Path::Normalized correctSoundPath(VFS::Path::NormalizedView resPath); // Adds "music\\". - std::string correctMusicPath(const std::string& resPath); + std::string correctMusicPath(std::string_view resPath); // Removes "meshes\\". std::string_view meshPathForESM3(std::string_view resPath); diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 936dd64679..2d22a7a034 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -99,4 +99,21 @@ namespace VFS ++normalized.back(); return { it, mIndex.lower_bound(normalized) }; } + + RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator(VFS::Path::NormalizedView path) const + { + if (path.value().empty()) + return { mIndex.begin(), mIndex.end() }; + const auto it = mIndex.lower_bound(path); + if (it == mIndex.end() || !it->first.view().starts_with(path.value())) + return { it, it }; + std::string copy(path.value()); + ++copy.back(); + return { it, mIndex.lower_bound(copy) }; + } + + RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator() const + { + return { mIndex.begin(), mIndex.end() }; + } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 59211602de..d64be1d1d1 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -65,6 +65,10 @@ namespace VFS /// @note May be called from any thread once the index has been built. RecursiveDirectoryRange getRecursiveDirectoryIterator(std::string_view path) const; + RecursiveDirectoryRange getRecursiveDirectoryIterator(VFS::Path::NormalizedView path) const; + + RecursiveDirectoryRange getRecursiveDirectoryIterator() const; + /// Retrieve the absolute path to the file /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. diff --git a/components/vfs/pathutil.hpp b/components/vfs/pathutil.hpp index 1696e656ad..50f16d1804 100644 --- a/components/vfs/pathutil.hpp +++ b/components/vfs/pathutil.hpp @@ -179,6 +179,12 @@ namespace VFS::Path return true; } + Normalized& operator=(NormalizedView value) + { + mValue = value.value(); + return *this; + } + Normalized& operator/=(NormalizedView value) { mValue.reserve(mValue.size() + value.value().size() + 1); @@ -258,6 +264,22 @@ namespace VFS::Path result /= rhs; return result; } + + struct Hash + { + using is_transparent = void; + + [[nodiscard]] std::size_t operator()(std::string_view sv) const { return std::hash{}(sv); } + + [[nodiscard]] std::size_t operator()(const std::string& s) const { return std::hash{}(s); } + + [[nodiscard]] std::size_t operator()(const Normalized& s) const { return std::hash{}(s.value()); } + + [[nodiscard]] std::size_t operator()(NormalizedView s) const + { + return std::hash{}(s.value()); + } + }; } #endif From 40cc16046b782df327a605a54e1857ee8fa52d1b Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 14 Apr 2024 19:51:10 +0200 Subject: [PATCH 414/451] Use normalized path for sound decoder --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 4 ++-- apps/openmw/mwsound/ffmpeg_decoder.hpp | 2 +- apps/openmw/mwsound/movieaudiofactory.cpp | 12 ++++-------- apps/openmw/mwsound/sound_decoder.hpp | 4 +++- apps/openmw/mwsound/soundmanagerimp.cpp | 2 +- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 9e7a2be3a8..e9a5a1eb94 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -208,7 +208,7 @@ namespace MWSound return dec; } - void FFmpeg_Decoder::open(const std::string& fname) + void FFmpeg_Decoder::open(VFS::Path::NormalizedView fname) { close(); mDataStream = mResourceMgr->get(fname); @@ -224,7 +224,7 @@ namespace MWSound formatCtx->pb = ioCtx.get(); // avformat_open_input frees user supplied AVFormatContext on failure - if (avformat_open_input(&formatCtx, fname.c_str(), nullptr, nullptr) != 0) + if (avformat_open_input(&formatCtx, fname.value().data(), nullptr, nullptr) != 0) throw std::runtime_error("Failed to open input"); AVFormatContextPtr formatCtxPtr(std::exchange(formatCtx, nullptr)); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index ed3297403e..264ff8fab7 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -93,7 +93,7 @@ namespace MWSound bool getAVAudioData(); size_t readAVAudioData(void* data, size_t length); - void open(const std::string& fname) override; + void open(VFS::Path::NormalizedView fname) override; void close() override; std::string getName() override; diff --git a/apps/openmw/mwsound/movieaudiofactory.cpp b/apps/openmw/mwsound/movieaudiofactory.cpp index 1bb5275c45..68ea221321 100644 --- a/apps/openmw/mwsound/movieaudiofactory.cpp +++ b/apps/openmw/mwsound/movieaudiofactory.cpp @@ -24,8 +24,10 @@ namespace MWSound private: MWSound::MovieAudioDecoder* mDecoder; - void open(const std::string& fname) override; - void close() override; + void open(VFS::Path::NormalizedView fname) override { throw std::runtime_error("Method not implemented"); } + + void close() override {} + std::string getName() override; void getInfo(int* samplerate, ChannelConfig* chans, SampleType* type) override; size_t read(char* buffer, size_t bytes) override; @@ -92,12 +94,6 @@ namespace MWSound std::shared_ptr mDecoderBridge; }; - void MWSoundDecoderBridge::open(const std::string& fname) - { - throw std::runtime_error("Method not implemented"); - } - void MWSoundDecoderBridge::close() {} - std::string MWSoundDecoderBridge::getName() { return mDecoder->getStreamName(); diff --git a/apps/openmw/mwsound/sound_decoder.hpp b/apps/openmw/mwsound/sound_decoder.hpp index f6dcdb7032..17f9d28909 100644 --- a/apps/openmw/mwsound/sound_decoder.hpp +++ b/apps/openmw/mwsound/sound_decoder.hpp @@ -1,6 +1,8 @@ #ifndef GAME_SOUND_SOUND_DECODER_H #define GAME_SOUND_SOUND_DECODER_H +#include + #include #include @@ -36,7 +38,7 @@ namespace MWSound { const VFS::Manager* mResourceMgr; - virtual void open(const std::string& fname) = 0; + virtual void open(VFS::Path::NormalizedView fname) = 0; virtual void close() = 0; virtual std::string getName() = 0; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 4c3f227ecd..039c283d7a 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -267,7 +267,7 @@ namespace MWSound DecoderPtr decoder = getDecoder(); try { - decoder->open(filename); + decoder->open(VFS::Path::Normalized(filename)); } catch (std::exception& e) { From 5b0eb0b5b0c231dcdf7fa13287f194ccf4148465 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 20 Apr 2024 14:15:24 +0200 Subject: [PATCH 415/451] Log ptr for which agent bounds are not supported To make it easier to find what NPC or mod makes this happen. --- apps/openmw/mwworld/scene.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 64a258cff8..beb519b9e6 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -190,7 +190,8 @@ namespace { const DetourNavigator::AgentBounds agentBounds = world.getPathfindingAgentBounds(ptr); if (!navigator.addAgent(agentBounds)) - Log(Debug::Warning) << "Agent bounds are not supported by navigator: " << agentBounds; + Log(Debug::Warning) << "Agent bounds are not supported by navigator for " << ptr.toString() << ": " + << agentBounds; } } From f9eefd9ac44cf954de099ea7486b66f71f44bf28 Mon Sep 17 00:00:00 2001 From: Joakim Berg Date: Sat, 20 Apr 2024 16:35:11 +0000 Subject: [PATCH 416/451] Swedish translation of the launcher --- files/lang/components_sv.ts | 95 +++ files/lang/launcher_sv.ts | 1494 +++++++++++++++++++++++++++++++++++ files/lang/wizard_sv.ts | 678 ++++++++++++++++ 3 files changed, 2267 insertions(+) create mode 100644 files/lang/components_sv.ts create mode 100644 files/lang/launcher_sv.ts create mode 100644 files/lang/wizard_sv.ts diff --git a/files/lang/components_sv.ts b/files/lang/components_sv.ts new file mode 100644 index 0000000000..8682a569bd --- /dev/null +++ b/files/lang/components_sv.ts @@ -0,0 +1,95 @@ + + + + + ContentSelector + + Select language used by ESM/ESP content files to allow OpenMW to detect their encoding. + Välj språk som används av ESM/ESP-innehållsfiler så att OpenMW kan hitta deras kodning. + + + + ContentSelectorModel::ContentModel + + Unable to find dependent file: %1 + Kunde inte hitta beroende fil: %1 + + + Dependent file needs to be active: %1 + Beroende fil måste vara aktiv: %1 + + + This file needs to load after %1 + Denna fil måste laddas efter %1 + + + + ContentSelectorModel::EsmFile + + <b>Author:</b> %1<br/><b>Format version:</b> %2<br/><b>Modified:</b> %3<br/><b>Path:</b><br/>%4<br/><br/><b>Description:</b><br/>%5<br/><br/><b>Dependencies: </b>%6<br/> + <b>Skapare:</b> %1<br/><b>Formatversion:</b> %2<br/><b>Ändrad:</b> %3<br/><b>Sökväg:</b><br/>%4<br/><br/><b>Beskrivning:</b><br/>%5<br/><br/><b>Beroenden: </b>%6<br/> + + + <br/><b>This content file cannot be disabled because it is part of OpenMW.</b><br/> + <br/><b>Denna innehållsfil kan inte inaktiveras då den är en del av OpenMW.</b><br/> + + + <br/><b>This content file cannot be disabled because it is enabled in a config file other than the user one.</b><br/> + <br/><b>Denna innehållsfil kan inte inaktiveras då den är en aktiverad i en annan konfigurationsfil än användarens.</b><br/> + + + + ContentSelectorView::ContentSelector + + &Check Selected + &Bocka i markerade + + + &Uncheck Selected + &Bocka ur markerade + + + &Copy Path(s) to Clipboard + &Kopiera sökväg(ar) till klippbordet + + + <No game file> + <Ingen spelfil> + + + + Process::ProcessInvoker + + Error starting executable + Kunde inte starta körbara filen + + + Error running executable + Fel när körbara filen skulle köras + + + +Arguments: + + +Argument: + + + + <html><head/><body><p><b>Could not find %1</b></p><p>The application is not found.</p><p>Please make sure OpenMW is installed correctly and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte hitta %1</b></p><p>Applikationen hittas inte.</p><p>Se till att OpenMW är korrekt installerat och försök igen.</p></body></html> + + + <html><head/><body><p><b>Could not start %1</b></p><p>The application is not executable.</p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte starta %1</b></p><p>Applikationen är inte körbar.</p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html> + + + <html><head/><body><p><b>Could not start %1</b></p><p>An error occurred while starting %1.</p><p>Press "Show Details..." for more information.</p></body></html> + <html><head/><body><p><b>Kunde inte starta %1</b></p><p>Ett fel uppstod när %1 skulle startas.</p><p>Tryck på "Visa detaljer..." för mer information.</p></body></html> + + + <html><head/><body><p><b>Executable %1 returned an error</b></p><p>An error occurred while running %1.</p><p>Press "Show Details..." for more information.</p></body></html> + <html><head/><body><p><b>Körbara filen %1 gav ett felmeddelande</b></p><p>Ett fel uppstod när %1 kördes.</p><p>Tryck på "Visa detaljer..." för mer information.</p></body></html> + + + diff --git a/files/lang/launcher_sv.ts b/files/lang/launcher_sv.ts new file mode 100644 index 0000000000..4cd75487b2 --- /dev/null +++ b/files/lang/launcher_sv.ts @@ -0,0 +1,1494 @@ + + + + + DataFilesPage + + Content Files + Innehållsfiler + + + Data Directories + Datakataloger + + + Scan directories for likely data directories and append them at the end of the list. + Skanna kataloger för troliga datakataloger och lägg till dem i slutet på listan. + + + Append + Lägg till + + + Scan directories for likely data directories and insert them above the selected position + Skanna kataloger för troliga datakataloger och lägg till dem ovanför den markerade positionen + + + Insert Above + Lägg till ovanför + + + Move selected directory one position up + Flytta markerad katalog en position upp + + + Move Up + Flytta upp + + + Move selected directory one position down + Flytta markerad katalog en position ner + + + Move Down + Flytta ner + + + Remove selected directory + Ta bort markerad katalog + + + Remove + Ta bort + + + Archive Files + Arkivfiler + + + Move selected archive one position up + Flytta markerat arkiv en position upp + + + Move selected archive one position down + Flytta markerat arkiv en position ner + + + Navigation Mesh Cache + Cache för navigeringsmesh + + + Generate navigation mesh cache for all content. Will be used by the engine to make cell loading faster. + Generera navigeringsmesh för allt innehåll. Kommer användas av motorn för att ladda celler snabbare. + + + Update + Uppdatera + + + Cancel navigation mesh generation. Already processed data will be saved. + Avbryt generering av navigationsmesh. Redan processad data kommer sparas. + + + Cancel + Avbryt + + + MiB + MiB + + + Content List + Innehållslista + + + Select a content list + Välj en innehållslista + + + New Content List + Ny innehållslista + + + &New Content List + &Ny innehållslista + + + Clone Content List + Klona innehållslista + + + Delete Content List + Radera innehållslista + + + Ctrl+N + Ctrl+N + + + Ctrl+G + Ctrl+G + + + Ctrl+D + Ctrl+D + + + Check Selection + Kryssa markering + + + Uncheck Selection + Avkryssa markering + + + Refresh Data Files + Uppdatera datafiler + + + Ctrl+R + Ctrl+R + + + <html><head/><body><p>Note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Notera: innehållsfiler som inte är del av aktuell innehållslista är <span style=" font-style:italic;font-weight: bold">markerade</span></p></body></html> + + + <html><head/><body><p>Note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Notera: kataloger som inte är del av aktuell innehållslista är <span style=" font-style:italic;font-weight: bold">markerade</span></p></body></html> + + + <html><head/><body><p>Note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> + <html><head/><body><p>Notera: arkiv som inte är del av aktuell innehållslista är <span style=" font-style:italic;font-weight: bold">markerade</span></p></body></html> + + + Remove Unused Tiles + Ta bort oanvända celler + + + Max Size + Maximal storlek + + + + GraphicsPage + + 0 + 0 + + + 2 + 2 + + + 4 + 4 + + + 8 + 8 + + + 16 + 16 + + + Custom: + Egen: + + + Standard: + Standard: + + + Fullscreen + Helskärm + + + Windowed Fullscreen + Helskärm i fönsterläge + + + Windowed + Fönster + + + Disabled + Inaktiverad + + + Enabled + Aktiverad + + + Adaptive + Adaptiv + + + FPS + FPS + + + × + × + + + Screen + Skärm + + + Resolution + Upplösning + + + Window Mode + Fönsterläge + + + Framerate Limit + Gränsvärde bilduppdateringsfrekvens + + + Window Border + Fönster, ram + + + Anti-Aliasing + Kantutjämning + + + Vertical Synchronization + Vertikal synkronisering + + + + ImportPage + + Form + Can be translated in different ways depending on context. Will return to later. + + + + Morrowind Installation Wizard + Installationsguide för Morrowind + + + Run &Installation Wizard + Kör &Installationsguide + + + Morrowind Settings Importer + Inställningsimporterare för Morrowind + + + Browse... + Bläddra... + + + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + Fonter som kommer med ordinarie spelmotor är suddiga med gränssnittsuppskalning och stödjer bara ett litet antal tecken, +så OpenMW tillhandahåller andra fonter för att undvika dessa problem. Dessa fonter använder TrueType-teknologi och är ganska lika +de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordinarie fonter istället för OpenMWs, alternativt om du använder egna bitmapfonter. + + + Run &Settings Importer + Kör &Inställningsimporterare + + + File to Import Settings From: + Fil att importera inställningar från: + + + Import Bitmap Fonts + Importera bitmapfonter + + + Import Add-on and Plugin Selection + Importera tillägg- och pluginmarkeringar + + + + Launcher::DataFilesPage + + English + Engelska + + + French + Franska + + + German + Tyska + + + Italian + Italienska + + + Polish + Polska + + + Russian + Ryska + + + Spanish + Spanska + + + New Content List + Ny innehållslista + + + Content List name: + Namn på innehållslista: + + + Clone Content List + Klona innehållslista + + + Select Directory + Välj katalog + + + Delete Content List + Radera innehållslista + + + Are you sure you want to delete <b>%1</b>? + Är du säker på att du vill radera <b>%1</b>? + + + Delete + Radera + + + Contains content file(s) + Innehåller innehållsfil(er) + + + Will be added to the current profile + Kommer läggas till nuvarande profil + + + &Check Selected + &Kryssa markerade + + + &Uncheck Selected + &Avkryssa markerade + + + Resolved as %1 + Löst som %1 + + + This is the data-local directory and cannot be disabled + Det här är den data-lokala katalogen och kan inte inaktiveras + + + This directory is part of OpenMW and cannot be disabled + Denna katalog är en del av OpenMW och kan inte inaktiveras + + + This directory is enabled in an openmw.cfg other than the user one + Denna katalog är aktiverad i en annan openmw.cfg än användarens + + + This archive is enabled in an openmw.cfg other than the user one + Detta arkiv är aktiverat i en annan openmw.cfg än användarens + + + + Launcher::GraphicsPage + + Error receiving number of screens + Det gick inte att ta emot antalet skärmar + + + <br><b>SDL_GetNumVideoDisplays failed:</b><br><br> + <br><b>SDL_GetNumVideoDisplays misslyckades:</b><br><br> + + + Screen + Skärm + + + Error receiving resolutions + Det gick inte att ta emot upplösningar + + + <br><b>SDL_GetNumDisplayModes failed:</b><br><br> + <br><b>SDL_GetNumDisplayModes misslyckades:</b><br><br> + + + <br><b>SDL_GetDisplayMode failed:</b><br><br> + <br><b>SDL_GetDisplayMode misslyckades:</b><br><br> + + + + Launcher::ImportPage + + Error writing OpenMW configuration file + Det gick inte att skriva en OpenMW-konfigurationsfil + + + Morrowind configuration file (*.ini) + Morrowind konfigurationsfil (*.ini) + + + Importer finished + Importeraren klar + + + Failed to import settings from INI file. + Misslyckades att importera inställningar från INI-fil. + + + <html><head/><body><p><b>Could not open or create %1 for writing </b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte öppna eller skapa %1 för att skriva </b></p><p>Kontrollera att du har rätt behörigheter och försök igen.</p></body></html> + + + + Launcher::MainDialog + + Close + Stäng + + + Launch OpenMW + Starta OpenMW + + + Help + Hjälp + + + Error opening OpenMW configuration file + Fel när OpenMW-konfigurationsfil skulle öppnas + + + First run + Första körning + + + Run &Installation Wizard + Kör &Installationsguide + + + Skip + Hoppa över + + + OpenMW %1 release + OpenMW version %1 + + + OpenMW development (%1) + OpenMW utvecklarversion (%1) + + + Compiled on %1 %2 + Kompilerad den %1 %2 + + + Error detecting Morrowind installation + Kunde inte hitta Morrowindinstallation + + + Run &Installation Wizard... + Kör &Installationsguide... + + + Error reading OpenMW configuration files + Fel när OpenMW-konfigurationsfil skulle läsas + + + Error writing OpenMW configuration file + Fel när OpenMW-konfigurationsfil skulle skrivas> + + + Error writing user settings file + Fel när användarinställningsfil skulle skrivas + + + Error writing Launcher configuration file + Fel när Startarens-konfigurationsfil skulle skrivas + + + No game file selected + Ingen spelfil vald + + + <html><head/><body><p><b>Welcome to OpenMW!</b></p><p>It is recommended to run the Installation Wizard.</p><p>The Wizard will let you select an existing Morrowind installation, or install Morrowind for OpenMW to use.</p></body></html> + <html><head/><body><p><b>Välkommen till OpenMW!</b></p><p>Det är rekommenderat att du kör Installationsguiden.</p><p>Installationsguiden låter dig välja en befintlig Morrowindinstallation eller installera Morrowind för OpenMW.</p></body></html> + + + <br><b>Could not open %0 for reading:</b><br><br>%1<br><br>Please make sure you have the right permissions and try again.<br> + <br><b>Kunde inte öppna %0 för läsning:</b><br><br>%1<br><br>Se till att du har rätt behörigheter och försök igen.<br> + + + <br><b>Could not open %0 for reading</b><br><br>Please make sure you have the right permissions and try again.<br> + <br><b>Kunde inte öppna %0 för läsning</b><br><br>Se till att du har rätt behörigheter och försök igen.<br> + + + <br><b>Could not find the Data Files location</b><br><br>The directory containing the data files was not found. + <br><b>Kunde inte hitta Data Files-platsen</b><br><br>Katalogen som innehåller datafilerna hittades inte. + + + <br>The problem may be due to an incomplete installation of OpenMW.<br>Reinstalling OpenMW may resolve the problem.<br> + <br>Problemet kan bero på en ofullständig installation av OpenMW.<br>Ominstallation av OpenMW kan lösa problemet.<br> + + + <br><b>Could not open or create %0 for writing</b><br><br>Please make sure you have the right permissions and try again.<br> + <br><b>Kunde inte öppna eller skapa %0 för att skriva</b><br><br>Se till att du har rätt behörigheter och försök igen.<br> + + + <br><b>You do not have a game file selected.</b><br><br>OpenMW will not start without a game file selected.<br> + <br><b>Du har ingen spelfil markerad.</b><br><br>OpenMW kan inte starta utan en spelfil markerad.<br> + + + Error creating OpenMW configuration directory: code %0 + Kunde inte skapa konfigurationskatalog för OpenMW: kod %0 + + + <br><b>Could not create directory %0</b><br><br>%1<br> + <br><b>Kunde inte skapa katalog %0</b><br><br>%1<br> + + + + Launcher::SettingsPage + + Text file (*.txt) + Textfil (*.txt) + + + + MainWindow + + OpenMW Launcher + OpenMW Startare + + + OpenMW version + OpenMW version + + + toolBar + toolBar + + + Data Files + Datafiler + + + Allows to setup data files and directories + Låter dig ställa in datafiler och kataloger + + + Settings + Inställningar + + + Allows to tweak engine settings + Låter dig justera spelmotorinställningar + + + Import + Importera + + + Allows to import data from original engine + Låter dig importera data från originalmotorn + + + Display + Bild + + + Allows to change display settings + Låter dig ändra bildinställningar + + + + QObject + + Select configuration file + Välj konfigurationsfil + + + Select script file + Välj skriptfil + + + + SelectSubdirs + + Select directories you wish to add + Välj kataloger du vill lägga till + + + + SettingsPage + + <html><head/><body><p>Make disposition change of merchants caused by trading permanent.</p></body></html> + <html><head/><body><p>Gör dispositionsändring av handlare orsakad av handel permanent.</p></body></html> + + + <html><head/><body><p>Make player followers and escorters start combat with enemies who have started combat with them or the player. Otherwise they wait for the enemies or the player to do an attack first.</p></body></html> + <html><head/><body><p>Gör att följare och eskorter till spelaren själva påbörjar strid med fiender som påbörjat strid med dem eller spelaren. Annars kommer de vänta tills fienden eller spelaren har attackerat dem först.</p></body></html> + + + <html><head/><body><p>Make Damage Fatigue magic effect uncapped like Drain Fatigue effect.</p><p>This means that unlike Morrowind you will be able to knock down actors using this effect.</p></body></html> + <html><head/><body><p>Gör att "Damage Fatigue"-magieffekten blir obegränsad såsom "Drain Fatigue"-effekten.</p><p>Det innebär att du, till skillnad från i Morrowind, kommer kunna slå ner figurer till marken med denna effekt.</p></body></html> + + + Uncapped Damage Fatigue + Obegränsad "Damage Fatigue" + + + <html><head/><body><p>Stops combat with NPCs affected by Calm spells every frame -- like in Morrowind without the MCP.</p></body></html> + <html><head/><body><p>Avbryter strid med icke-spelbara figurer påverkade av "Calm"-besvjärjelser varje bildruta – såsom i Morrowind utan MCP.</p></body></html> + + + <html><head/><body><p>Make the value of filled soul gems dependent only on soul magnitude.</p></body></html> + <html><head/><body><p>Gör värderingen av fyllda "Soulgems" endast baserad på själens magnitud.</p></body></html> + + + <html><head/><body><p>Makes player swim a bit upward from the line of sight. Applies only in third person mode. Intended to make simpler swimming without diving.</p></body></html> + <html><head/><body><p>Får spelaren att simma något uppåt från siktlinjen. Endast applicerat på tredjepersonsperspektivet. Avsett att göra simning enklare.</p></body></html> + + + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> + <html><head/><body><p>Gör det möjligt att stjäla föremål från icke-spelbara figurer som är avsvimmade.</p></body></html> + + + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>Om aktiv kommer en en navigeringsmesh av världsgeometrin byggas i bakgrunden som används för pathfinding. Om inaktiv kommer endast pathgrid användas för att bygga vägar. Enkelkärnade processorer kan få kraftigt försämrad prestanda. Kan påverka prestandan på flerkärniga processorer något. Flerkärniga processorer kan ha olika fördröjning för att uppdatera navigeringsmesh. Förflyttning mellan externa världar och att gå in eller ut ur en plats producerar en uppdatering av navigeringsmesh. Icke-spelbara figurer kan inte hitta vägar innan navigeringsmesh har skapats runt om dem. Testa att inaktivera denna funktion om du vill ha en mer gammaldags AI som inte vet var den ska gå när du står bakom den där stenen och skjuter ett eldklot.</p></body></html> + + + <html><head/><body><p>If enabled NPCs apply evasion maneuver to avoid collisions with others.</p></body></html> + <html><head/><body><p>Om aktiverat kommer icke-spelbara figurer göra undanmanövrar för att undvika kollisioner med andra.</p></body></html> + + + <html><head/><body><p>Don't use race weight in NPC movement speed calculations.</p></body></html> + <html><head/><body><p>Ta inte med varelsers vikt i beräkningen av hastighet.</p></body></html> + + + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> + <html><head/><body><p>Om denna inställning är aktiv tillåts spelaren plundra figurer (exempelvis tillkallade varelser) under deras dödsanimation, om de inte är i strid.</p><p>Om inställningen är inaktiv måste spelaren vänta tills dödsanimationen är slut. Detta gör det mycket svårare att exploatera tillkallade varelser (exempelvis plundra Draemoror eller Golden Saints för att få dyrbara vapen). Inställningen är i konflikt med skyltdocks-moddar som använder SkipAnim för att förhindra avslutning av dödsanimationer.</p></body></html> + + + <html><head/><body><p>Makes unarmed creature attacks able to reduce armor condition, just as attacks from NPCs and armed creatures.</p></body></html> + <html><head/><body><p>Gör att obeväpnade varelseattacker kan reducera rustningars skick, precis som attacker från icke-spelbara figurer och beväpnade varelser.</p></body></html> + + + Off + Av + + + <html><head/><body><p>How many threads will be spawned to compute physics update in the background. A value of 0 means that the update will be performed in the main thread.</p><p>A value greater than 1 requires the Bullet library be compiled with multithreading support.</p></body></html> + <html><head/><body><p>Antalet exekveringstrådar som kommer användas för att beräkna fysikuppdateringar i bakgrunden.</p><p>Ett värde högre än 1 kräver att Bullet-biblioteket är kompilerat med multithreading-stöd.</p></body></html> + + + Cylinder + Cylinder + + + Visuals + Visuellt + + + Animations + Animationer + + + <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + <html><head/><body><p>Använd animationer för magiska föremål, precis som för besvärjelser.</p></body></html> + + + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> + <html><head/><body><p>Gör spelarens och icke-spelbara figurers rörelser mjukare. Rekommenderas att användas tillsammans med "vänd mot rörelseriktningen" aktiverad.</p></body></html> + + + <html><head/><body><p>Load per-group KF-files and skeleton files from Animations folder</p></body></html> + <html><head/><body><p>Ladda per-grupp KF-filer och skelettfiler från Animations-katalogen</p></body></html> + + + <html><head/><body><p>Affects side and diagonal movement. Enabling this setting makes movement more realistic.</p><p>If disabled then the whole character's body is pointed to the direction of view. Diagonal movement has no special animation and causes sliding.</p><p>If enabled then the character turns lower body to the direction of movement. Upper body is turned partially. Head is always pointed to the direction of view. In combat mode it works only for diagonal movement. In non-combat mode it changes straight right and straight left movement as well. Also turns the whole body up or down when swimming according to the movement direction.</p></body></html> + <html><head/><body><p>Påverkar sido- och diagonalförflyttningar. Förflyttningar blir mer realistiska om aktiv.</p><p>Om aktiv kommer spelarrollfiguren vända underkroppen i rikting mot förflyttningen. Överkroppen vänds delvis. Huvudet pekar alltid dit kameran ser. I stidsläge fungerar det bara för diagonal förflyttning. Utanför strid ändrar inställningen förflyttningar rakt höger och vänster också. Inställningen vänder också hela kroppen upp eller ner vid simning enligt förflyttningsriktningen.</p></body></html> + + + <html><head/><body><p>Render holstered weapons (with quivers and scabbards), requires modded assets.</p></body></html> + <html><head/><body><p>Rendera hölstrade vapen (med koger och vapenslidor), kräver moddat innehåll.</p></body></html> + + + <html><head/><body><p>Render holstered shield, requires modded assets.</p></body></html> + <html><head/><body><p>Rendera hölstrade sköldar, kräver moddat innehåll.</p></body></html> + + + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + <html><head/><body><p>I tredjepersonsperspektiv kommer kameran svänga efter spelarens förflyttningsanimationer. Detta var det förvalda beteendet i OpenMW 0.48.0 och tidigare.</p></body></html> + + + Shaders + Shader + + + <html><head/><body><p>If this option is enabled, normal maps are automatically recognized and used if they are named appropriately + (see 'normal map pattern', e.g. for a base texture foo.dds, the normal map texture would have to be named foo_n.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file (.nif or .osg file). Affects objects.</p></body></html> + <html><head/><body><p>Om funktionen är aktiv kommer normalkartor (normal maps) att hittas och användas automatiskt om de har korrekt namn + (se 'normal map pattern', t.ex. för en bastextur foo.dds ska normalkartan heta foo_n.dds). + Om funktionen är inaktiverad kommer normalkartor bara användas om texturerna är explicit listade i 3D-modell-filen (.nif eller .osg fil). Påverkar objekt.</p></body></html> + + + <html><head/><body><p>See 'auto use object normal maps'. Affects terrain.</p></body></html> + <html><head/><body><p>Se 'autoanvänd normalkartor (normal maps) på objekt'. Påverkar terräng.</p></body></html> + + + <html><head/><body><p>If this option is enabled, specular maps are automatically recognized and used if they are named appropriately + (see 'specular map pattern', e.g. for a base texture foo.dds, + the specular map texture would have to be named foo_spec.dds). + If this option is disabled, normal maps are only used if they are explicitly listed within the mesh file + (.osg file, not supported in .nif files). Affects objects.</p></body></html> + <html><head/><body><p>Om den här funktionen är aktiverad kommer spekularitetskartor (specular maps) att hittas och användas + (see 'specular map pattern', t.ex. för en bastextur foo.dds, + ska spekularitetskartan heta foo_spec.dds). + Om funktionen är inaktiverad kommer normalkartor bara användas om texturerna är explicit listade i 3D-modell-filen + (.nif eller .osg fil). Påverkar objekt.</p></body></html> + + + <html><head/><body><p>If a file with pattern 'terrain specular map pattern' exists, use that file as a 'diffuse specular' map. The texture must contain the layer colour in the RGB channel (as usual), and a specular multiplier in the alpha channel.</p></body></html> + <html><head/><body><p>Om en fil med mönstret 'terrain specular map pattern' finns, använd den filen som en 'diffuse specular'-karta. Texturen måste innehålla färglagren i RGB-kanalerna (som vanligt) och spekularitet i alfakanalen.</p></body></html> + + + <html><head/><body><p>Normally environment map reflections aren't affected by lighting, which makes environment-mapped (and thus bump-mapped objects) glow in the dark. + Morrowind Code Patch includes an option to remedy that by doing environment-mapping before applying lighting, this is the equivalent of that option. + Affected objects will use shaders. + </p></body></html> + <html><head/><body><p>Normalt sett påverkas omgivningskartors reflektioner inte av ljus, vilket gör att omgivningskartor (och således så kallade bump mappade objekt) glöder i mörkret. + Morrowind Code Patch inkluderar en inställning att kringå detta genom att lägga omgivningskartor före ljussättningen. Det här är motsvarigheten till den inställningen. + Påverkade objekt kommer använda shaders. + </p></body></html> + + + <html><head/><body><p>Allows MSAA to work with alpha-tested meshes, producing better-looking edges without pixelation. Can negatively impact performance.</p></body></html> + <html><head/><body><p>Gör att MSAA fungerar med alfa-testade modeller, vilket ger snyggare kanter utan synliga pixlar. Kan påverka prestandan negativt.</p></body></html> + + + <html><head/><body><p>Enables soft particles for particle effects. This technique softens the intersection between individual particles and other opaque geometry by blending between them.</p></body></html> + <html><head/><body><p>Aktiverar mjuka partiklar på partikeleffekter. Denna teknik mjukar upp övergången mellan individuella partiklar och annan ogenomskinlig geometri genom att smälta samman dem.</p></body></html> + + + <html><head/><body><p>Simulate coverage-preserving mipmaps to prevent alpha-tested meshes shrinking as they get further away. Will cause meshes whose textures have coverage-preserving mipmaps to grow, though, so refer to mod installation instructions for how to set this.</p></body></html> + <html><head/><body><p>Simulerar mip maps med coverage-preserving för att förhindra att alfa-testade modeller krymper när de kommer längre bort. Kommer göra att dessa modeller växer istället; se instruktioner i modinstallationen för hur detta ska ställas in.</p></body></html> + + + <html><head/><body><p>EXPERIMENTAL: Stop rain and snow from falling through overhangs and roofs.</p></body></html> + <html><head/><body><p>EXPERIMENTELLT: Förhindra att regn och snö faller genom tak och överhäng.</p></body></html> + + + Fog + Dimma + + + <html><head/><body><p>By default, the fog becomes thicker proportionally to your distance from the clipping plane set at the clipping distance, which causes distortion at the edges of the screen. + This setting makes the fog use the actual eye point distance (or so called Euclidean distance) to calculate the fog, which makes the fog look less artificial, especially if you have a wide FOV.</p></body></html> + <html><head/><body><p>Som standard blir dimman tätare proportionellt med ditt avstånd till avklippningsdistansen, vilket ger distortion vid skärmens kanter. + Denna inställning gör att dimman använder det faktiska ögonpunktsavståndet (så kallat euklidiskt avstånd) för att beräkna dimman, vilket får dimman att se mindre artificiell ut, särskilt vid hög FoV.</p></body></html> + + + <html><head/><body><p>Use exponential fog formula. By default, linear fog is used.</p></body></html> + <html><head/><body><p>Använd exponentiell formel för dimma. Som standard används linjär dimma.</p></body></html> + + + <html><head/><body><p>Reduce visibility of clipping plane by blending objects with the sky.</p></body></html> + <html><head/><body><p>Reducera synligheten av avklippsplanet genom att smälta samman objekt med himmelen.</p></body></html> + + + <html><head/><body><p>The fraction of the maximum distance at which blending with the sky starts.</p></body></html> + <html><head/><body><p>Fraktionen av det maximala avståndet där utsmetning mellan himmel och horisont påbörjas.</p></body></html> + + + Terrain + Terräng + + + <html><head/><body><p>Controls how large an object must be to be visible in the scene. The object’s size is divided by its distance to the camera and the result of the division is compared with this value. The smaller this value is, the more objects you will see in the scene.</p></body></html> + <html><head/><body><p>Bestämmer hur stort ett objekt måste vara för att vara synligt på skärmen. Objektets storlek divideras med sitt avstånd till kameran. Resultatet av denna division jämförs med detta värde. Ju mindre värdet är, desto fler objekt kommer du se i scenen.</p></body></html> + + + <html><head/><body><p>If true, use paging and LOD algorithms to display the entire terrain. If false, only display terrain of the loaded cells.</p></body></html> + <html><head/><body><p>Om aktiverat används paging och LOD-algoritmer för att rita upp all terräng. Om inaktiverat visas endast terrängen i den laddade cellen.</p></body></html> + + + <html><head/><body><p>Use object paging for active cells grid.</p></body></html> + <html><head/><body><p>Använd objekt-paging för aktiva cellers rutnät.</p></body></html> + + + <html><head/><body><p>If this setting is true, supporting models will make use of day night switch nodes.</p></body></html> + <html><head/><body><p>Om den här inställningen är aktiv kommer modeller med stöd för det att använda dag- och natt-bytesnoder.</p></body></html> + + + Post Processing + Efterbehandling + + + <html><head/><body><p>If this setting is true, post processing will be enabled.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer efterbehandling (post processing) att vara aktiverat.</p></body></html> + + + <html><head/><body><p>Re-render transparent objects with forced alpha clipping.</p></body></html> + <html><head/><body><p>Återrendera transparenta objekt med forcerad alpha clipping.</p></body></html> + + + <html><head/><body><p>Controls how much eye adaptation can change from frame to frame. Smaller values makes for slower transitions.</p></body></html> + <html><head/><body><p>Bestämmer hur mycket ögonanpassningen kan förändras från bildruta till bildruta. Lägre värden ger långsammare övergångar.</p></body></html> + + + Audio + Ljud + + + Select your preferred audio device. + Välj din föredragna ljudenhet. + + + Default + Förvalt + + + This setting controls HRTF, which simulates 3D sound on stereo systems. + Denna inställning kontrollerar HRTF, vilket simulerar 3D-ljud på stereosystem. + + + HRTF + HRTF + + + Automatic + Automatisk + + + On + + + + Select your preferred HRTF profile. + Välj din föredragna HRTF-profil. + + + Interface + Gränssnitt + + + <html><head/><body><p>This setting scales GUI windows. A value of 1.0 results in the normal scale.</p></body></html> + <html><head/><body><p>Denna inställning skalar grafiska fönster i gränssnittet. Ett värde på 1.0 ger den normala skalan.</p></body></html> + + + <html><head/><body><p>Show the remaining duration of magic effects and lights if this setting is true. The remaining duration is displayed in the tooltip by hovering over the magical effect. </p><p>The default value is false.</p></body></html> + <html><head/><body><p>Visar den återstående tiden för magiska effekter och ljus om denna inställning är på. Den återstående tiden visas som en inforuta när muspekaren befinner sig över den magiska effekten. </p><p>Förvalt är av.</p></body></html> + + + <html><head/><body><p>If this setting is true, dialogue topics will have a different color if the topic is specific to the NPC you're talking to or the topic was previously seen. Color can be changed in settings.cfg.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer dialogämnen ha en annan färg om ämnet är specifikt till den icke-spelbara figur du pratar med eller om ämnet redan har setts. Färger kan ändras i settings.cfg.</p><p>Förvalt är av.</p></body></html> + + + Size of characters in game texts. + Storlek på tecken i speltext. + + + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + <html><head/><body><p>Aktivera zoomning på lokala och globala kartor.</p></body></html> + + + <html><head/><body><p>If this setting is true, containers supporting graphic herbalism will do so instead of opening the menu.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer behållare som stödjer grafisk örtplockning (graphic herbalism) använda den funktionen istället för att öppna menyn.</p></body></html> + + + <html><head/><body><p>If this setting is true, damage bonus of arrows and bolts will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer skadebonus från pilar visas på föremålens inforuta.</p><p>Förvalt är av.</p></body></html> + + + <html><head/><body><p>If this setting is true, melee weapons reach and speed will be shown on item tooltip.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Om denna inställning är på kommer närstridsvapens räckvidd och hastighet att visas på föremåls inforuta.</p><p>Förvalt är av.</p></body></html> + + + <html><head/><body><p>Stretch menus, load screens, etc. to the window aspect ratio.</p></body></html> + <html><head/><body><p>Sträck ut menyer, laddskärmar o.s.v. till fönstrets aspektratio.</p></body></html> + + + <html><head/><body><p>Whether or not the chance of success will be displayed in the enchanting menu.</p><p>The default value is false.</p></body></html> + <html><head/><body><p>Huruvida chansen att lyckas kommer visas i förtrollningsmenyn.</p><p>Förvalt är av.</p></body></html> + + + <html><head/><body><p>Prevents merchants from equipping items that are sold to them.</p></body></html> + <html><head/><body><p>Förhindrar att handlare tar på sig föremål som säljs till dem.</p></body></html> + + + <html><head/><body><p>Trainers now only choose which skills to train using their base skill points, allowing mercantile improving effects to be used without making mercantile an offered skill.</p></body></html> + <html><head/><body><p>Tränare väljer nu endast de färdigheter som kan tränas genom att använda deras basfärdighetsvärde, vilket tillåter förbättring av merkantil utan att göra merkantil till en erbjuden färdighet.</p></body></html> + + + Miscellaneous + Diverse + + + Saves + Sparfiler + + + <html><head/><body><p>This setting determines whether the amount of the time the player has spent playing will be displayed for each saved game in the Load menu.</p></body></html> + <html><head/><body><p>Denna inställning avgör huruvida mängden tid spelaren har spenderat i spelet kommer visas för varje sparat spel i Ladda spel-menyn.</p></body></html> + + + JPG + JPG + + + PNG + PNG + + + TGA + TGA + + + Testing + Testning + + + These settings are intended for testing mods and will cause issues if used for normal gameplay. + Dessa inställningar är avsedda för att testa moddar och kommer orsaka problem vid normalt spelande. + + + <html><head/><body><p>OpenMW will capture control of the cursor if this setting is true.</p><p>In “look mode”, OpenMW will center the cursor regardless of the value of this setting (since the cursor/crosshair is always centered in the OpenMW window). However, in GUI mode, this setting determines the behavior when the cursor is moved outside the OpenMW window. If true, the cursor movement stops at the edge of the window preventing access to other applications. If false, the cursor is allowed to move freely on the desktop.</p><p>This setting does not apply to the screen where escape has been pressed, where the cursor is never captured. Regardless of this setting “Alt-Tab” or some other operating system dependent key sequence can be used to allow the operating system to regain control of the mouse cursor. This setting interacts with the minimize on focus loss setting by affecting what counts as a focus loss. Specifically on a two-screen configuration it may be more convenient to access the second screen with setting disabled.</p><p>Note for developers: it’s desirable to have this setting disabled when running the game in a debugger, to prevent the mouse cursor from becoming unusable when the game pauses on a breakpoint.</p></body></html> + <html><head/><body><p>OpenMW kommer ta kontroll av muspekaren om denna inställning är aktiverad.</p><p>I ”tittläge” kommer OpenMW centrera muspekaren oavsett värdet på denna inställning (eftersom muspekaren/hårkorset alltid är centrerat i OpenMW-fönstret). I gränssnittsläge däremot kommer denna inställning bedöma beteendet när muspekaren flyttas utanför OpenMW-fönstret. Om på kommer muspekarrörelsen stanna vid kanten av fönstret, vilket förhindrar tillgång till andra applikationer. Om av tillåts muspekaren att röras fritt över skrivbordet.</p><p>Denna inställning appliceras inte på skärmen där Escape har blivit tryckt, då muspekaren aldrig tas över. Oavsett denna inställning kan ”Alt-Tab” eller annan operativsystemberoende knappsekvens användas för att ge operativsystemet åter tillgång till muspekaren. Denna inställning interagerar med minimera vid fokusförlust-inställningen genom att påverka vad som räknas som en fokusförlust. Specifikt på en tvåskärmskonfiguration kan det vara mer smidigt att få tillgång till den andra skärmen med inställningen inaktiverad.</p><p>Notis för utvecklare: det är önskvärt att ha denna inställning inaktiverad när OpenMW körs i debug-läge för att förhindra att musen blir oanvändbar när spelet pausar vid en brytpunkt.</p></body></html> + + + Browse… + Bläddra… + + + Collision is used for both physics simulation and navigation mesh generation for pathfinding. Cylinder gives the best consistency between available navigation paths and ability to move by them. Changing this value affects navigation mesh generation therefore navigation mesh disk cache generated for one value will not be useful with another. + Kollision används för både fysiksimulering och navigationsmeshgenerering för pathfinding. Cylinder ger bäst förenlighet mellan tillgängliga navigeringsvägar och möjlighet att förflytta förbi dem. Ändring av detta värde påverkar navigeringsmeshgenereringen – därför kommer inte navigeringsmesh disk cache för ett värde vara användbart för ett annat. + + + Gameplay + Spelmekanik + + + <html><head/><body><p>If enabled, a magical ammunition is required to bypass normal weapon resistance or weakness. If disabled, a magical ranged weapon or a magical ammunition is required.</p></body></html> + <html><head/><body><p>Om aktiverad kommer magisk ammunition krävas för att förbigå normal vapenmotståndskraft eller -svaghet. Om inaktiverad krävs ett magiskt avståndsvapen eller en magisk ammunition.</p></body></html> + + + <html><head/><body><p>Make enchanted weapons without Magical flag bypass normal weapons resistance, like in Morrowind.</p></body></html> + <html><head/><body><p>Få förtrollade vapen utan Magisk-flagga att förbigå normal vapenmotståndskraft, såsom i Morrowind.</p></body></html> + + + cells + celler + + + Shadows + Skuggor + + + <html><head/><body><p>Type of "compute scene bounds" computation method to be used. Bounds (default) for good balance between performance and shadow quality, primitives for better looking shadows or none for no computation.</p></body></html> + <html><head/><body><p>Typ av "compute scene bounds" beräkningsmetod att använda. Bounds (förvalt) för en bra balans mellan prestanda och skuggkvalitet, primitives för snyggare skuggor eller none för ingen beräkning alls.</p></body></html> + + + <html><head/><body><p>64 game units is 1 real life yard or about 0.9 m</p></body></html> + <html><head/><body><p>64 spelenheter är 1 yard eller ungefär 0,9 meter i verkligheten</p></body></html> + + + unit(s) + enhet(er) + + + <html><head/><body><p>Enable shadows for NPCs and creatures besides the player character. May have a minor performance impact.</p></body></html> + <html><head/><body><p>Aktiverar skuggor för icke-spelbara figurer och varelser bortsett från spelarrollfiguren. Kan ha en liten negativ prestandapåverkan.</p></body></html> + + + 512 + 512 + + + 1024 + 1024 + + + 2048 + 2048 + + + 4096 + 4096 + + + <html><head/><body><p>The fraction of the limit above at which shadows begin to gradually fade away.</p></body></html> + <html><head/><body><p>Den fraktion av gränsen ovan vid vilken skuggor gradvis börjar blekna bort.</p></body></html> + + + <html><head/><body><p>Enable shadows exclusively for the player character. May have a very minor performance impact.</p></body></html> + <html><head/><body><p>Aktivera skuggor exklusivt för spelarrollfiguren. Kan ha en mycket liten negativ prestandapåverkan.</p></body></html> + + + <html><head/><body><p>The resolution of each individual shadow map. Increasing it significantly improves shadow quality but may have a minor performance impact.</p></body></html> + <html><head/><body><p>Upplösningen för varje individuell skuggkarta. Ökning av den ökar skuggkvalitén signifikant, men kan ha en lättare negativ prestandapåverkan.</p></body></html> + + + <html><head/><body><p>The distance from the camera at which shadows completely disappear.</p></body></html> + <html><head/><body><p>Avståndet från kameran vid vilken skuggor helt försvinner.</p></body></html> + + + <html><head/><body><p>Enable shadows for primarily inanimate objects. May have a significant performance impact.</p></body></html> + <html><head/><body><p>Aktivera skuggor för huvudsakligen icke-rörliga objekt. Kan ha en signifikant negativ prestandapåverkan.</p></body></html> + + + <html><head/><body><p>Due to limitations with Morrowind's data, only actors can cast shadows indoors, which some might feel is distracting.</p><p>Has no effect if actor/player shadows are not enabled.</p></body></html> + <html><head/><body><p>På grund av begränsningar med Morrowinds data kan endast figurer ge ifrån sig skuggor inomhus, vilket vissa kan tycka vara distraherande.</p><p>Har ingen effekt om figur/spelarskuggor inte är aktiverat.</p></body></html> + + + <html><head/><body><p>Enable shadows for the terrain including distant terrain. May have a significant performance and shadow quality impact.</p></body></html> + <html><head/><body><p>Aktivera skuggor för terräng, inklusive avlägsen terräng. Kan ha en signifikant negativ påverkan på prestanda och skuggkvalité.</p></body></html> + + + Lighting + Ljussättning + + + Tooltip + Inforuta + + + Crosshair + Hårkors + + + Screenshots + Skärmdumpar + + + <html><head/><body><p>Give actors an ability to swim over the water surface when they follow other actor independently from their ability to swim. Has effect only when nav mesh building is enabled.</p></body></html> + <html><head/><body><p>Ge figurer en möjlighet att simma över vattenytan när de följer andra figurer, oberoende av deras förmåga att simma. Har endast effekt när navigeringsmesh är aktiverat.</p></body></html> + + + <html><head/><body><p>Effects of reflected Absorb spells are not mirrored - like in Morrowind.</p></body></html> + <html><head/><body><p>Effekter av reflekterade "Absorb"-besvärjelser speglas inte – såsom i Morrowind.</p></body></html> + + + <html><head/><body><p>Maximum distance at which lights will appear (measured in units).</p><p>Set this to 0 to use an unlimited distance.</p></body></html> + <html><head/><body><p>Maximala avståndet där ljuskällor syns (mätt i enheter).</p><p>Värdet 0 ger oändligt avstånd.</p></body></html> + + + <html><head/><body><p>Maximum number of lights per object.</p><p>A low number near default will cause light popping similar to what you would see with legacy lighting.</p></body></html> + <html><head/><body><p>Maximalt antal ljuskällor per objekt.</p><p>Ett lågt tal nära det förvalda kommer orsaka att ljuskällor poppar upp som vid ljussättningsmetoden Gammaldags.</p></body></html> + + + <html><head/><body><p>Fraction of maximum distance at which lights will start to fade.</p><p>Set this to a low value for slower transitions or a high value for quicker transitions.</p></body></html> + <html><head/><body><p>Fraktion av det maximala avståndet från vilket ljuskällor börjar blekna.</p><p>Välj ett lågt värde för långsammare övergång eller högre värde för snabbare övergång.</p></body></html> + + + <html><head/><body><p>Set the internal handling of light sources.</p> +<p> "Legacy" always uses 8 lights per object and provides a lighting closest to an original game.</p> +<p>"Shaders (compatibility)" removes the 8 light limit. This mode also enables lighting on groundcover and a configurable light fade. It is recommended to use this with older hardware and a light limit closer to 8.</p> +<p> "Shaders" carries all of the benefits that "Shaders (compatibility)" does, but uses a modern approach that allows for a higher max lights count with little to no performance penalties on modern hardware.</p></body></html> + <html><head/><body><p>Välj intern hantering av ljuskällor.</p> +<p> "Gammaldags" använder alltid max 8 ljuskällor per objekt och ger ljussättning likt ett gammaldags spel.</p> +<p>"Shader (kompatibilitet)" tar bort begränsningen med max 8 ljuskällor per objekt. Detta läge aktiverar också ljus på marktäckning och ett konfigurerbart ljusbleknande. Rekommenderas för äldre hårdvara tillsammans med en ljusbegränsning nära 8.</p> +<p> "Shader" har alla fördelar som "Shader (kompatibilitet)" har, men med ett modernt förhållningssätt som möjliggör fler maximalt antal ljuskällor med liten eller ingen prestandaförlust på modern hårdvara.</p></body></html> + + + Legacy + Gammaldags + + + Shaders (compatibility) + Shader (kompatibilitet) + + + <html><head/><body><p>Multipler for bounding sphere of lights.</p><p>Higher numbers allows for smooth falloff but require an increase in number of max lights.</p><p>Does not effect the illumination or strength of lights.</p></body></html> + <html><head/><body><p>Multiplikator för ljusens gränssfär.</p><p>Högre värden ger mjukare minskning av gränssfären, men kräver högre värde i Max antal ljuskällor.</p><p>Påverkar inte ljusstyrkan.</p></body></html> + + + <html><head/><body><p>Minimum ambient interior brightness.</p><p>Increase this if you feel interiors are too dark.</p></body></html> + <html><head/><body><p>Minsta omgivande ljusstyrka i interiörer.</p><p>Öka värdet om du anser att interiörer är för mörka.</p></body></html> + + + In third-person view, use the camera as the sound listener instead of the player character. + Använd kameran som ljudlyssnare istället för spelarrollfiguren i tredjepersonsperspektivet. + + + Permanent Barter Disposition Changes + Permanenta handelsförändringar + + + Classic Calm Spells Behavior + Klassiskt beteende för "Calm"-besvärjelser + + + NPCs Avoid Collisions + Icke-spelbara figurer undviker kollisioner + + + Soulgem Values Rebalance + Ombalansering av "Soulgems" värdering + + + Day Night Switch Nodes + Dag/natt-bytesnoder + + + Followers Defend Immediately + Följare försvarar omedelbart + + + Only Magical Ammo Bypass Resistance + Bara magisk ammunition förbigår motstånd + + + Graphic Herbalism + Grafisk örtplockning + + + Swim Upward Correction + Simma uppåt korrigering + + + Enchanted Weapons Are Magical + Förtrollade vapen är magiska + + + Merchant Equipping Fix + Handlare klär inte på sig + + + Can Loot During Death Animation + Kan plundra under dödsanimation + + + Classic Reflected Absorb Spells Behavior + Klassiskt beteende för reflekterade "Absorb"-besvärjelser + + + Unarmed Creature Attacks Damage Armor + Obeväpnad attack från varelser skadar rustning + + + Affect Werewolves + Påverka varulvar + + + Do Not Affect Werewolves + Påverka inte varulvar + + + Background Physics Threads + Fysiktrådar i bakgrunden + + + Actor Collision Shape Type + Figurers kollisionsformtyp + + + Axis-Aligned Bounding Box + Axeljusterad begränsningslåda + + + Rotating Box + Roterande låda + + + Smooth Movement + Mjuka rörelser + + + Use Additional Animation Sources + Använd extra animationskällor + + + Weapon Sheathing + Vapenhölstring + + + Shield Sheathing + Sköldhölstring + + + Player Movement Ignores Animation + Spelarförflyttningar ignorerar animation + + + Use Magic Item Animation + Animationer för magiska föremål + + + Auto Use Object Normal Maps + Använd automatiskt normalkartor på objekt + + + Soft Particles + Mjuka partiklar + + + Auto Use Object Specular Maps + Använd automatiskt spekularitetskartor på objekt + + + Auto Use Terrain Normal Maps + Använd automatiskt normalkartor på terräng + + + Auto Use Terrain Specular Maps + Använd automatiskt spekularitetskartor på terräng + + + Use Anti-Aliased Alpha Testing + Använd kantutjämnad alfa-testning + + + Bump/Reflect Map Local Lighting + Bump/Reflektionskartor lokalt ljus + + + Weather Particle Occlusion + Regn/snö blockeras av tak + + + Exponential Fog + Exponentiell dimma + + + Radial Fog + Radiell dimma + + + Sky Blending Start + Smeta ut horisont/himmel, start + + + Sky Blending + Smeta ut horisont/himmel + + + Object Paging Min Size + Object paging needs a better translation + Minsta storlek för objektpaging + + + Viewing Distance + Siktavstånd + + + Distant Land + Avlägsen terräng + + + Active Grid Object Paging + Object paging needs a better translation + Objektpaging i aktivt rutnät + + + Transparent Postpass + Will return to later + + + + Auto Exposure Speed + Autoexponeringshastighet + + + Enable Post Processing + Aktivera efterbehandling (post processing) + + + Shadow Planes Computation Method + Skuggplaner beräkningsmetod + + + Enable Actor Shadows + Aktivera rollfigurskuggor + + + Fade Start Multiplier + Blekningsstartmultiplikator + + + Enable Player Shadows + Aktivera spelarskuggor + + + Shadow Map Resolution + Skuggkartsupplösning + + + Shadow Distance Limit: + Skuggavståndsgräns: + + + Enable Object Shadows + Aktivera objektskuggor + + + Enable Indoor Shadows + Aktivera interiöra skuggor + + + Enable Terrain Shadows + Aktivera terrängskuggor + + + Maximum Light Distance + Maximalt ljusavstånd + + + Max Lights + Max antal ljuskällor + + + Lighting Method + Ljussättningsmetod + + + Bounding Sphere Multiplier + Gränssfärsmultiplikator + + + Minimum Interior Brightness + Minsta ljusstyrka i interiörer + + + Audio Device + Ljudenhet + + + HRTF Profile + HRTF-profil + + + Tooltip and Crosshair + Inforuta och hårkors + + + GUI Scaling Factor + Skalningsfaktor för gränssnitt + + + Show Effect Duration + Visa effektvaraktighet + + + Change Dialogue Topic Color + Ändra färg på dialogämnen + + + Font Size + Fontstorlek + + + Show Projectile Damage + Visa projektilskada + + + Show Melee Info + Visa närstridsinfo + + + Stretch Menu Background + Sträck ut menybakgrund + + + Show Owned Objects + Visa ägda objekt + + + Show Enchant Chance + Visa chans för "Enchant" + + + Maximum Quicksaves + Max snabbsparfiler + + + Screenshot Format + Skärmdumpformat + + + Grab Cursor + Ta över muspekaren + + + Default Cell + Förinställd cell + + + Bounds + Bounds + + + Primitives + Primitives + + + None + None + + + Always Allow Actors to Follow over Water + Tillåt alltid figurer att följa över vatten + + + Racial Variation in Speed Fix + Fix för varelsers hastighetsvariation + + + Use Navigation Mesh for Pathfinding + Använd navigeringsmesh för pathfinding + + + Trainers Choose Offered Skills by Base Value + Tränare väljer erbjudna färdigheter efter grundvärde + + + Steal from Knocked out Actors in Combat + Stjäl från avsvimmade rollfigurer i strid + + + Factor Strength into Hand-to-Hand Combat + Räkna in styrka i obeväpnad strid + + + Turn to Movement Direction + Vänd mot rörelseriktningen + + + Adjust Coverage for Alpha Test + Justera täckning för alfa-testning + + + Use the Camera as the Sound Listener + Använd kameran som ljudlyssnaren + + + Can Zoom on Maps + Kan zooma på kartor + + + Add "Time Played" to Saves + Lägg till spelad tid i sparfiler + + + Notify on Saved Screenshot + Ge notis vid sparad skärmdump + + + Skip Menu and Generate Default Character + Hoppa över meny och generera förinställd rollfigur + + + Start Default Character at + Starta förinställd rollfigur vid + + + Run Script After Startup: + Kör skript efter uppstart: + + + diff --git a/files/lang/wizard_sv.ts b/files/lang/wizard_sv.ts new file mode 100644 index 0000000000..7f2f4f67fc --- /dev/null +++ b/files/lang/wizard_sv.ts @@ -0,0 +1,678 @@ + + + + + ComponentSelectionPage + + WizardPage + WizardPage + + + Select Components + Välj komponenter + + + Which components should be installed? + Vilka komponenter ska installeras? + + + <html><head/><body><p>Select which official Morrowind expansions should be installed. For best results, it is recommended to have both expansions installed.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to install expansions later by re-running this Wizard.<br/></p></body></html> + <html><head/><body><p>Välj vilka officiella Morrowindexpansioner som ska installeras. För bäst resultat rekommenderas att båda expansionerna installeras.</p><p><span style=" font-weight:bold;">Note:</span> Det är möjligt att installera expansioner senare genom att köra denna guide igen.<br/></p></body></html> + + + Selected components: + Valda komponenter: + + + + ConclusionPage + + WizardPage + WizardPage + + + Completing the OpenMW Wizard + Färdigställer OpenMWs installationsguide + + + Placeholder + Placeholder + + + + ExistingInstallationPage + + WizardPage + WizardPage + + + Select Existing Installation + Välj befintlig installation + + + Select an existing installation for OpenMW to use or modify. + Välj en befintlig installation som OpenMW kan använda eller modifiera. + + + Detected installations: + Hittade installationer: + + + Browse... + Bläddra... + + + + ImportPage + + WizardPage + WizardPage + + + Import Settings + Importera inställningar + + + Import settings from the Morrowind installation. + Importera inställningar från Morrowindinstallationen. + + + <html><head/><body><p>OpenMW needs to import settings from the Morrowind configuration file in order to function properly.</p><p><span style=" font-weight:bold;">Note:</span> It is possible to import settings later by re-running this Wizard.</p><p/></body></html> + <html><head/><body><p>OpenMW behöver importera inställningar från Morrowinds konfigurationsfil för att fungera korrekt.</p><p><span style=" font-weight:bold;">Notera:</span> Det är möjligt att importera inställningarna senare genom att köra denna guide igen.</p><p/></body></html> + + + Import Settings From Morrowind.ini + Importera inställningar från Morrowind.ini + + + Import Add-on and Plugin Selection + Importera tillägg- och pluginmarkering + + + Import Bitmap Fonts Setup From Morrowind.ini + Importera bitmapfonter från Morrowind.ini + + + Fonts shipped with the original engine are blurry with UI scaling and support only a small amount of characters, +so OpenMW provides another set of fonts to avoid these issues. These fonts use TrueType technology and are quite similar +to default Morrowind fonts. Check this box if you still prefer original fonts over OpenMW ones or if you use custom bitmap fonts. + Fonter som kommer med ordinarie spelmotor är suddiga med gränssnittsuppskalning och stödjer bara ett litet antal tecken, +så OpenMW tillhandahåller därför andra fonter för att undvika dessa problem. Dessa fonter använder TrueType-teknologi och är ganska lika +de ordinarie fonterna i Morrowind. Bocka i denna ruta om du ändå föredrar ordinarie fonter istället för OpenMWs, alternativt om du använder egna bitmapfonter. + + + + InstallationPage + + WizardPage + WizardPage + + + Installing + Installerar + + + Please wait while Morrowind is installed on your computer. + Vänta medan Morrowind installeras på din dator. + + + + InstallationTargetPage + + WizardPage + WizardPage + + + Select Installation Destination + Välj installationsplats + + + Where should Morrowind be installed? + Var ska Morrowind installeras? + + + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + + + Morrowind will be installed to the following location. + Morrowind kommer installeras på följande plats. + + + Browse... + Bläddra... + + + + IntroPage + + WizardPage + WizardPage + + + Welcome to the OpenMW Wizard + Välkommen till OpenMWs installationsguide + + + This Wizard will help you install Morrowind and its add-ons for OpenMW to use. + Denna guide hjälper dig att installera Morrowind och expansionerna för OpenMW. + + + + LanguageSelectionPage + + WizardPage + WizardPage + + + Select Morrowind Language + Välj Morrowinds språk + + + What is the language of the Morrowind installation? + Vad är språket på Morrowindinstallationen? + + + <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> + + + Select the language of the Morrowind installation. + Välj språket på Morrowindinstallationen. + + + + MethodSelectionPage + + WizardPage + WizardPage + + + Select Installation Method + Välj installationsmetod + + + <html><head/><body><p>Select how you would like to install <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> + <html><head/><body><p>Välj hur du vill installera <i>The Elder Scrolls III: Morrowind</i>.</p></body></html> + + + Retail CD/DVD + Köpt CD/DVD + + + <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> + + + Install from a retail disc to a new location. + Installera från en köpt skiva till en ny plats. + + + Existing Installation + Befintlig installation + + + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> + + + Select an existing installation. + Välj en befintlig installation. + + + Don't have a copy? + Äger du inte spelet? + + + <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> + <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> + + + Buy the game + Köp spelet + + + + QObject + + <br><b>Could not find Morrowind.ini</b><br><br>The Wizard needs to update settings in this file.<br><br>Press "Browse..." to specify the location manually.<br> + <br><b>Kunde inte hitta Morrowind.ini</b><br><br>Guiden behöver uppdatera inställningarna i denna fil.<br><br>Tryck på "Bläddra..." för att specificera filens plats manuellt.<br> + + + B&rowse... + B&läddra... + + + Select configuration file + Välj konfigurationsfil + + + <b>Morrowind.bsa</b> is missing!<br>Make sure your Morrowind installation is complete. + <b>Morrowind.bsa</b> saknas!<br>Se till att din Morrowindinstallation är komplett. + + + <br><b>There may be a more recent version of Morrowind available.</b><br><br>Do you wish to continue anyway?<br> + <br><b>Det kan finnas nyare version av Morrowind tillgänglig.</b><br><br>Vill du fortsätta ändå?<br> + + + Most recent Morrowind not detected + Senaste versionen av Morrowind hittades inte + + + Select a valid %1 installation media.<br><b>Hint</b>: make sure that it contains at least one <b>.cab</b> file. + Välj ett giltigt %1 installationsmedium.<br><b>Tips</b>: säkerställ att det finns åtminstone en <b>.cab</b>-fil. + + + There may be a more recent version of Morrowind available.<br><br>Do you wish to continue anyway? + Det kan finnas en mer uppdaterad version av Morrowind tillgänglig.<br><br>Vill du fortsätta ändå? + + + + Wizard::ComponentSelectionPage + + &Install + &Installera + + + &Skip + &Hoppa över + + + Morrowind (installed) + Morrowind (installerat) + + + Morrowind + Morrowind + + + Tribunal (installed) + Tribunal (installerat) + + + Tribunal + Tribunal + + + Bloodmoon (installed) + Bloodmoon (installerat) + + + Bloodmoon + Bloodmoon + + + About to install Tribunal after Bloodmoon + På väg att installera Tribunal efter Bloodmoon + + + <html><head/><body><p><b>You are about to install Tribunal</b></p><p>Bloodmoon is already installed on your computer.</p><p>However, it is recommended that you install Tribunal before Bloodmoon.</p><p>Would you like to re-install Bloodmoon?</p></body></html> + <html><head/><body><p><b>Du håller på att installera Tribunal</b></p><p>Bloodmoon finns redan installerat på din dator.</p><p>Det är dock rekommenderat att du installerar Tribunal före Bloodmoon.</p><p>Vill du ominstallera Bloodmoon?</p></body></html> + + + Re-install &Bloodmoon + Ominstallera &Bloodmoon + + + + Wizard::ConclusionPage + + <html><head/><body><p>The OpenMW Wizard successfully installed Morrowind on your computer.</p></body></html> + <html><head/><body><p>OpenMWs Installationsguide har installerat Morrowind på din dator.</p></body></html> + + + <html><head/><body><p>The OpenMW Wizard successfully modified your existing Morrowind installation.</body></html> + <html><head/><body><p>OpenMWs installationsguide har justerat din befintliga Morrowindinstallation.</body></html> + + + <html><head/><body><p>The OpenMW Wizard failed to install Morrowind on your computer.</p><p>Please report any bugs you might have encountered to our <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Make sure to include the installation log.</p><br/></body></html> + <html><head/><body><p>OpenMWs installationsguide misslyckades med att installera Morrowind på din dator.</p><p>Vänligen rapportera eventuella buggar på vår <a href="https://gitlab.com/OpenMW/openmw/issues">bug tracker</a>.<br/>Se till att du inkluderar installationsloggen.</p><br/></body></html> + + + + Wizard::ExistingInstallationPage + + No existing installations detected + Inga befintliga installationer hittades + + + Error detecting Morrowind configuration + Kunde inte hitta Morrowind-konfigurationsfil + + + Morrowind configuration file (*.ini) + Morrowind konfigurationsfil (*.ini) + + + Select Morrowind.esm (located in Data Files) + Markera Morrowind.esm (hittas i Data Files) + + + Morrowind master file (Morrowind.esm) + Morrowind masterfil (Morrowind.esm) + + + Error detecting Morrowind files + Kunde inte hitta Morrowindfiler + + + + Wizard::InstallationPage + + <p>Attempting to install component %1.</p> + <p>Försöker installera komponent %1.</p> + + + Attempting to install component %1. + Försöker installera komponent %1. + + + %1 Installation + %1 Installation + + + Select %1 installation media + Välj %1 installationsmedia + + + <p><br/><span style="color:red;"><b>Error: The installation was aborted by the user</b></span></p> + <p><br/><span style="color:red;"><b>Fel: Installationen avbröts av användaren</b></span></p> + + + <p>Detected old version of component Morrowind.</p> + <p>Hittade gammal version av Morrowind.</p> + + + Detected old version of component Morrowind. + Hittade gammal version av komponenten Morrowind. + + + Morrowind Installation + Installation av Morrowind + + + Installation finished + Installationen klar + + + Installation completed successfully! + Installationen slutfördes! + + + Installation failed! + Installationen misslyckades! + + + <p><br/><span style="color:red;"><b>Error: %1</b></p> + <p><br/><span style="color:red;"><b>Fel: %1</b></p> + + + <p><span style="color:red;"><b>%1</b></p> + <p><span style="color:red;"><b>%1</b></p> + + + <html><head/><body><p><b>The Wizard has encountered an error</b></p><p>The error reported was:</p><p>%1</p><p>Press &quot;Show Details...&quot; for more information.</p></body></html> + <html><head/><body><p><b>Guiden har stött på ett fel</b></p><p>Felet som rapporterades var:</p><p>%1</p><p>Tryck på &quot;Visa detaljer...&quot; för mer information.</p></body></html> + + + An error occurred + Ett fel uppstod + + + + Wizard::InstallationTargetPage + + Error creating destination + Fel när målkatalogen skulle skapas + + + <html><head/><body><p><b>Could not create the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> + <html><head/><body><p><b>Kunde inte skapa målkatalogen</b></p><p>Se till att du har rätt behörigheter och försök igen, eller specificera en annan katalog.</p></body></html> + + + <html><head/><body><p><b>Could not write to the destination directory</b></p><p>Please make sure you have the right permissions and try again, or specify a different location.</p></body></html> + <html><head/><body><p><b>Kunde inte skriva till målkatalogen</b></p><p>Se till att du har rätt behörigheter och försök igen, eller specificera en annan katalog.</p></body></html> + + + <html><head/><body><p><b>The destination directory is not empty</b></p><p>An existing Morrowind installation is present in the specified location.</p><p>Please specify a different location, or go back and select the location as an existing installation.</p></body></html> + <html><head/><body><p><b>Målkatalogen är inte tom</b></p><p>Det finns en befintlig Morrowindinstallation i den specificerade katalogen.</p><p>Välj en annan katalog eller gå tillbaka och välj katalogen som en befintlig installation.</p></body></html> + + + Insufficient permissions + Otillräckliga behörigheter + + + Destination not empty + Platsen är inte tom + + + Select where to install Morrowind + Välj där Morrowind ska installeras + + + + Wizard::LanguageSelectionPage + + English + Engelska + + + French + Franska + + + German + Tyska + + + Italian + Italienska + + + Polish + Polska + + + Russian + Ryska + + + Spanish + Spanska + + + + Wizard::MainWizard + + OpenMW Wizard + OpenMW installationsguide + + + Error opening Wizard log file + Kunde inte öppna guidens loggfil + + + <html><head/><body><p><b>Could not open %1 for writing</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte öppna %1 för att skriva</b></p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html> + + + <html><head/><body><p><b>Could not open %1 for reading</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte öppna %1 för läsning</b></p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html> + + + Error opening OpenMW configuration file + Fel när OpenMW-konfigurationsfil skulle öppnas + + + Quit Wizard + Avsluta guiden + + + Are you sure you want to exit the Wizard? + Är du säker på att du vill avsluta guiden? + + + Error creating OpenMW configuration directory + Fel vid skapande av OpenMW-konfigurationskatalog + + + <html><head/><body><p><b>Could not create %1</b></p><p>Please make sure you have the right permissions and try again.</p></body></html> + <html><head/><body><p><b>Kunde inte skapa %1</b></p><p>Se till att du har rätt behörigheter och försök igen.</p></body></html> + + + Error writing OpenMW configuration file + Kunde inte skriva OpenMW-konfigurationsfil + + + + Wizard::UnshieldWorker + + Failed to open Morrowind configuration file! + Kunde inte öppna en Morrowind-konfigurationsfil! + + + Opening %1 failed: %2. + Öppnar %1 misslyckad: %2. + + + Failed to write Morrowind configuration file! + Kunde inte skriva en Morrowind-konfigurationsfil! + + + Writing to %1 failed: %2. + Skriver till %1 misslyckad: %2. + + + Installing: %1 + Installerar: %1 + + + Installing: %1 directory + Installerar: %1 katalog + + + Installation finished! + Installationen slutförd! + + + Component parameter is invalid! + Komponentparametern är ogiltig! + + + An invalid component parameter was supplied. + En ogiltig komponentparameter angavs. + + + Failed to find a valid archive containing %1.bsa! Retrying. + Misslyckades att hitta ett giltigt arkiv som innehåller %1.bsa! Försöker igen. + + + Installing %1 + Installerar %1 + + + Installation media path not set! + Sökväg till installationsmedia inte inställd! + + + The source path for %1 was not set. + Källsökvägen för %1 ställdes inte in. + + + Cannot create temporary directory! + Kan inte skapa temporär katalog! + + + Failed to create %1. + Kunde inte skapa %1. + + + Cannot move into temporary directory! + Kan inte flytta till temporär katalog! + + + Failed to move into %1. + Misslyckades att flytta till %1. + + + Moving installation files + Flyttar installationsfiler + + + Could not install directory! + Kunde inte installera katalog! + + + Installing %1 to %2 failed. + Installation %1 till %2 misslyckades. + + + Could not install translation file! + Kunde inte installera översättningsfil! + + + Failed to install *%1 files. + Kunde inte installera *%1 filer. + + + Could not install Morrowind data file! + Kunde inte installera Morrowind-datafil! + + + Failed to install %1. + Misslyckades att installera %1. + + + Could not install Morrowind configuration file! + Kunde inte installera Morrowind-konfigurationsfil! + + + Installing: Sound directory + Installerar: Ljudkatalog + + + Could not find Tribunal data file! + Tribunal-datafil hittades inte! + + + Failed to find %1. + Misslyckades att hitta %1. + + + Could not find Tribunal patch file! + Tribunal-patchfil hittades inte! + + + Could not find Bloodmoon data file! + Bloodmoon-datafil hittades inte! + + + Updating Morrowind configuration file + Uppdaterar Morrowind-konfigurationsfil + + + %1 installation finished! + %1 installation klar! + + + Extracting: %1 + Extraherar: %1 + + + Failed to open InstallShield Cabinet File. + Misslyckades att öppna en InstallShield Cabinet-fil. + + + Opening %1 failed. + Misslyckades att öppna %1. + + + Failed to extract %1. + Misslyckades att extrahera %1. + + + Complete path: %1 + Färdigställ sökväg: %1 + + + From aea7b10986343bca77ae74c9f2892c9cf7161dc6 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 17 Apr 2024 22:53:55 +0300 Subject: [PATCH 417/451] Add dummy BGSM/BGEM file reader --- components/CMakeLists.txt | 4 + components/bgsm/file.cpp | 20 +++++ components/bgsm/file.hpp | 164 +++++++++++++++++++++++++++++++++++++ components/bgsm/reader.cpp | 35 ++++++++ components/bgsm/reader.hpp | 25 ++++++ components/bgsm/stream.cpp | 110 +++++++++++++++++++++++++ components/bgsm/stream.hpp | 143 ++++++++++++++++++++++++++++++++ 7 files changed, 501 insertions(+) create mode 100644 components/bgsm/file.cpp create mode 100644 components/bgsm/file.hpp create mode 100644 components/bgsm/reader.cpp create mode 100644 components/bgsm/reader.hpp create mode 100644 components/bgsm/stream.cpp create mode 100644 components/bgsm/stream.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 68411be2fc..084deaea58 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -107,6 +107,10 @@ add_component_dir (settings windowmode ) +add_component_dir (bgsm + reader stream file + ) + add_component_dir (bsa bsa_file compressedbsafile ba2gnrlfile ba2dx10file ba2file memorystream ) diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp new file mode 100644 index 0000000000..870d9e4067 --- /dev/null +++ b/components/bgsm/file.cpp @@ -0,0 +1,20 @@ +#include "file.hpp" + +#include "stream.hpp" + +namespace Bgsm +{ + void MaterialFile::read(BGSMStream& stream) + { + } + + void BGSMFile::read(BGSMStream& stream) + { + MaterialFile::read(stream); + } + + void BGEMFile::read(BGSMStream& stream) + { + MaterialFile::read(stream); + } +} diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp new file mode 100644 index 0000000000..117b135e4f --- /dev/null +++ b/components/bgsm/file.hpp @@ -0,0 +1,164 @@ +#ifndef OPENMW_COMPONENTS_BGSM_FILE_HPP +#define OPENMW_COMPONENTS_BGSM_FILE_HPP + +#include +#include +#include +#include + +#include +#include +#include + +namespace Bgsm +{ + class BGSMStream; + + enum class ShaderType + { + Lighting, + Effect, + }; + + struct MaterialFile + { + ShaderType mShaderType; + std::uint32_t mVersion; + std::uint32_t mClamp; + osg::Vec2f mUVOffset, mUVScale; + float mTransparency; + std::uint8_t mSourceBlendMode; + std::uint32_t mDestinationBlendMode; + std::uint32_t mAlphaTestMode; + std::uint8_t mAlphaTestRef; + bool mAlphaTest; + bool mDepthWrite, mDepthTest; + bool mSSR; + bool mWetnessControlSSR; + bool mDecal; + bool mTwoSided; + bool mDecalNoFade; + bool mNonOccluder; + bool mRefraction; + bool mRefractionFalloff; + float mRefractionPower; + bool mEnvMap; + float mEnvMapMaskScale; + bool mDepthBias; + bool mGrayscaleToPaletteColor; + std::uint8_t mMaskWrites; + + MaterialFile() = default; + virtual void read(BGSMStream& stream); + virtual ~MaterialFile() {} + }; + + struct BGSMFile : MaterialFile + { + std::string mDiffuseMap; + std::string mNormalMap; + std::string mSmoothSpecMap; + std::string mGreyscaleMap; + std::string mGlowMap; + std::string mWrinkleMap; + std::string mSpecularMap; + std::string mLightingMap; + std::string mFlowMap; + std::string mDistanceFieldAlphaMap; + std::string mEnvMap; + std::string mInnerLayerMap; + std::string mDisplacementMap; + bool mEnableEditorAlphaRef; + bool mTranslucency; + bool mTranslucencyThickObject; + bool mTranslucencyMixAlbedoWithSubsurfaceColor; + osg::Vec4f mTranslucencySubsurfaceColor; + float mTranslucencyTransmissiveScale; + float mTranslucencyTurbulence; + bool mRimLighting; + float mRimPower; + float mBackLightPower; + bool mSursurfaceLighting; + float mSubsurfaceLightingRolloff; + bool mSpecularEnabled; + osg::Vec4f mSpecularColor; + float mSpecularMult; + float mSmoothness; + float mFresnelPower; + float mWetnessControlSpecScale; + float mWetnessControlSpecPowerScale; + float mWetnessControlSpecMinvar; + float mWetnessControlEnvMapScale; + float mWetnessControlFresnelPower; + float mWetnessControlMetalness; + bool mPBR; + bool mCustomPorosity; + float mPorosityValue; + std::string mRootMaterialPath; + bool mAnisoLighting; + bool mEmitEnabled; + osg::Vec4f mEmittanceColor; + float mEmittanceMult; + bool mModelSpaceNormals; + bool mExternalEmittance; + float mLumEmittance; + bool mUseAdaptiveEmissive; + osg::Vec3f mAdaptiveEmissiveExposureParams; + bool mBackLighting; + bool mReceiveShadows; + bool mHideSecret; + bool mCastShadows; + bool mDissolveFade; + bool mAssumeShadowmask; + bool mHasGlowMap; + bool mEnvMapWindow; + bool mEnvMapEye; + bool mHair; + osg::Vec4f mHairTintColor; + bool mTree; + bool mFacegen; + bool mSkinTint; + bool mTessellate; + osg::Vec2f mDisplacementMapParams; + osg::Vec3f mTesselationParams; + float mGrayscaleToPaletteScale; + bool mSkewSpecularAlpha; + bool mTerrain; + osg::Vec3f mTerrainParams; + + void read(BGSMStream& stream) override; + }; + + struct BGEMFile : MaterialFile + { + std::string mBaseMap; + std::string mGrayscaleMap; + std::string mEnvMap; + std::string mNormalMap; + std::string mEnvMapMask; + std::string mSpecularMap; + std::string mLightingMap; + std::string mGlowMap; + bool mBlood; + bool mEffectLighting; + bool mFalloff; + bool mFalloffColor; + bool mGrayscaleToPaletteAlpha; + bool mSoft; + osg::Vec4f mBaseColor; + float mBaseColorScale; + osg::Vec4f mFalloffParams; + float mLightingInfluence; + std::uint8_t mEnvmapMinLOD; + float mSoftDepth; + osg::Vec4f mEmittanceColor; + osg::Vec3f mAdaptiveEmissiveExposureParams; + bool mHasGlowMap; + bool mEffectPbrSpecular; + + void read(BGSMStream& stream) override; + }; + + using MaterialFilePtr = std::shared_ptr; +} +#endif diff --git a/components/bgsm/reader.cpp b/components/bgsm/reader.cpp new file mode 100644 index 0000000000..c89d872bd7 --- /dev/null +++ b/components/bgsm/reader.cpp @@ -0,0 +1,35 @@ +#include "reader.hpp" + +#include +#include +#include + +#include "file.hpp" +#include "stream.hpp" + +namespace Bgsm +{ + void Reader::parse(Files::IStreamPtr&& inputStream) + { + BGSMStream stream(*this, std::move(inputStream)); + + std::array signature; + stream.readArray(signature); + std::string shaderType(signature.data(), 4); + if (shaderType == "BGEM") + { + mFile = std::make_unique(); + mFile->mShaderType = Bgsm::ShaderType::Effect; + } + else if (shaderType == "BGSM") + { + mFile = std::make_unique(); + mFile->mShaderType = Bgsm::ShaderType::Lighting; + } + else + throw std::runtime_error("Invalid material file"); + + mFile->read(stream); + } + +} diff --git a/components/bgsm/reader.hpp b/components/bgsm/reader.hpp new file mode 100644 index 0000000000..5ac67c0467 --- /dev/null +++ b/components/bgsm/reader.hpp @@ -0,0 +1,25 @@ +#ifndef OPENMW_COMPONENTS_BGSM_READER_HPP +#define OPENMW_COMPONENTS_BGSM_READER_HPP + +#include +#include +#include +#include + +#include + +#include "file.hpp" + +namespace Bgsm +{ + class Reader + { + std::unique_ptr mFile; + + public: + void parse(Files::IStreamPtr&& stream); + + std::uint32_t getVersion() const { return mFile->mVersion; } + }; +} +#endif diff --git a/components/bgsm/stream.cpp b/components/bgsm/stream.cpp new file mode 100644 index 0000000000..d8434fcb8a --- /dev/null +++ b/components/bgsm/stream.cpp @@ -0,0 +1,110 @@ +#include "stream.hpp" + +#include + +#include "reader.hpp" + +namespace +{ + + // Read a range of elements into a dynamic buffer per-element + // This one should be used if the type cannot be read contiguously + // (e.g. quaternions) + template + void readRange(Bgsm::BGSMStream& stream, T* dest, size_t size) + { + for (T& value : std::span(dest, size)) + stream.read(value); + } + + // Read a range of elements into a dynamic buffer + // This one should be used if the type can be read contiguously as an array of a different type + // (e.g. osg::VecXf can be read as a float array of X elements) + template + void readAlignedRange(Files::IStreamPtr& stream, T* dest, size_t size) + { + static_assert(std::is_standard_layout_v); + static_assert(std::alignment_of_v == std::alignment_of_v); + static_assert(sizeof(T) == sizeof(elementType) * numElements); + Bgsm::readDynamicBufferOfType(stream, reinterpret_cast(dest), size * numElements); + } + +} + +namespace Bgsm +{ + + std::uint32_t BGSMStream::getVersion() const + { + return mReader.getVersion(); + } + + std::string BGSMStream::getSizedString(size_t length) + { + std::string str(length, '\0'); + mStream->read(str.data(), length); + if (mStream->bad()) + throw std::runtime_error("Failed to read sized string of " + std::to_string(length) + " chars"); + size_t end = str.find('\0'); + if (end != std::string::npos) + str.erase(end); + return str; + } + + void BGSMStream::getSizedStrings(std::vector& vec, size_t size) + { + vec.resize(size); + for (size_t i = 0; i < vec.size(); i++) + vec[i] = getSizedString(); + } + + template <> + void BGSMStream::read(osg::Vec2f& vec) + { + readBufferOfType(mStream, vec._v); + } + + template <> + void BGSMStream::read(osg::Vec3f& vec) + { + readBufferOfType(mStream, vec._v); + } + + template <> + void BGSMStream::read(osg::Vec4f& vec) + { + readBufferOfType(mStream, vec._v); + } + + template <> + void BGSMStream::read(std::string& str) + { + str = getSizedString(); + } + + template <> + void BGSMStream::read(osg::Vec2f* dest, size_t size) + { + readAlignedRange(mStream, dest, size); + } + + template <> + void BGSMStream::read(osg::Vec3f* dest, size_t size) + { + readAlignedRange(mStream, dest, size); + } + + template <> + void BGSMStream::read(osg::Vec4f* dest, size_t size) + { + readAlignedRange(mStream, dest, size); + } + + template <> + void BGSMStream::read(std::string* dest, size_t size) + { + for (std::string& value : std::span(dest, size)) + value = getSizedString(); + } + +} diff --git a/components/bgsm/stream.hpp b/components/bgsm/stream.hpp new file mode 100644 index 0000000000..8b0e1efdbe --- /dev/null +++ b/components/bgsm/stream.hpp @@ -0,0 +1,143 @@ +#ifndef OPENMW_COMPONENTS_BGSM_STREAM_HPP +#define OPENMW_COMPONENTS_BGSM_STREAM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace Bgsm +{ + class Reader; + + template + inline void readBufferOfType(Files::IStreamPtr& pIStream, T* dest) + { + static_assert(std::is_arithmetic_v, "Buffer element type is not arithmetic"); + pIStream->read((char*)dest, numInstances * sizeof(T)); + if (pIStream->bad()) + throw std::runtime_error("Failed to read typed (" + std::string(typeid(T).name()) + ") buffer of " + + std::to_string(numInstances) + " instances"); + if constexpr (Misc::IS_BIG_ENDIAN) + for (std::size_t i = 0; i < numInstances; i++) + Misc::swapEndiannessInplace(dest[i]); + } + + template + inline void readBufferOfType(Files::IStreamPtr& pIStream, T (&dest)[numInstances]) + { + readBufferOfType(pIStream, static_cast(dest)); + } + + template + inline void readDynamicBufferOfType(Files::IStreamPtr& pIStream, T* dest, std::size_t numInstances) + { + static_assert(std::is_arithmetic_v, "Buffer element type is not arithmetic"); + pIStream->read((char*)dest, numInstances * sizeof(T)); + if (pIStream->bad()) + throw std::runtime_error("Failed to read typed (" + std::string(typeid(T).name()) + ") dynamic buffer of " + + std::to_string(numInstances) + " instances"); + if constexpr (Misc::IS_BIG_ENDIAN) + for (std::size_t i = 0; i < numInstances; i++) + Misc::swapEndiannessInplace(dest[i]); + } + + class BGSMStream + { + const Reader& mReader; + Files::IStreamPtr mStream; + + public: + explicit BGSMStream( + const Reader& reader, Files::IStreamPtr&& stream) + : mReader(reader) + , mStream(std::move(stream)) + { + } + + const Reader& getFile() const { return mReader; } + + std::uint32_t getVersion() const; + + void skip(size_t size) { mStream->ignore(size); } + + /// Read into a single instance of type + template + void read(T& data) + { + readBufferOfType<1>(mStream, &data); + } + + /// Read multiple instances of type into an array + template + void readArray(std::array& arr) + { + readBufferOfType(mStream, arr.data()); + } + + /// Read instances of type into a dynamic buffer + template + void read(T* dest, size_t size) + { + readDynamicBufferOfType(mStream, dest, size); + } + + /// Read multiple instances of type into a vector + template + void readVector(std::vector& vec, size_t size) + { + if (size == 0) + return; + vec.resize(size); + read(vec.data(), size); + } + + /// Extract an instance of type + template + T get() + { + T data; + read(data); + return data; + } + + /// Read a string of the given length + std::string getSizedString(size_t length); + + /// Read a string of the length specified in the file + std::string getSizedString() { return getSizedString(get()); } + + /// Read a list of strings + void getSizedStrings(std::vector& vec, size_t size); + }; + + template <> + void BGSMStream::read(osg::Vec2f& vec); + template <> + void BGSMStream::read(osg::Vec3f& vec); + template <> + void BGSMStream::read(osg::Vec4f& vec); + template <> + void BGSMStream::read(std::string& str); + + template <> + void BGSMStream::read(osg::Vec2f* dest, size_t size); + template <> + void BGSMStream::read(osg::Vec3f* dest, size_t size); + template <> + void BGSMStream::read(osg::Vec4f* dest, size_t size); + template <> + void BGSMStream::read(std::string* dest, size_t size); +} + +#endif From 4a03555d53d6794d077470e33a65367ca8f61dba Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 01:00:49 +0300 Subject: [PATCH 418/451] Add BGEM/BGSM file support to niftest --- apps/niftest/niftest.cpp | 50 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index b37d85d739..3cd4382121 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -36,6 +37,13 @@ bool isNIF(const std::filesystem::path& filename) { return hasExtension(filename, ".nif") || hasExtension(filename, ".kf"); } + +/// Check if the file is a material file. +bool isMaterial(const std::filesystem::path& filename) +{ + return hasExtension(filename, ".bgem") || hasExtension(filename, ".bgsm"); +} + /// See if the file has the "bsa" extension. bool isBSA(const std::filesystem::path& filename) { @@ -81,6 +89,36 @@ void readNIF( } } + +void readMaterial( + const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) +{ + const std::string pathStr = Files::pathToUnicodeString(path); + if (!quiet) + { + if (hasExtension(path, ".bgem")) + std::cout << "Reading BGEM file '" << pathStr << "'"; + else + std::cout << "Reading BGSM file '" << pathStr << "'"; + if (!source.empty()) + std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; + std::cout << std::endl; + } + const std::filesystem::path fullPath = !source.empty() ? source / path : path; + try + { + Bgsm::Reader reader; + if (vfs != nullptr) + reader.parse(vfs->get(pathStr)); + else + reader.parse(Files::openConstrainedFileStream(fullPath)); + } + catch (std::exception& e) + { + std::cerr << "Failed to read '" << pathStr << "':" << std::endl << e.what() << std::endl; + } +} + /// Check all the nif files in a given VFS::Archive /// \note Can not read a bsa file inside of a bsa file. void readVFS(std::unique_ptr&& archive, const std::filesystem::path& archivePath, bool quiet) @@ -101,6 +139,10 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat { readNIF(archivePath, name.value(), &vfs, quiet); } + else if (isMaterial(name.value())) + { + readMaterial(archivePath, name.value(), &vfs, quiet); + } } if (!archivePath.empty() && !isBSA(archivePath)) @@ -129,10 +171,10 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat bool parseOptions(int argc, char** argv, Files::PathContainer& files, Files::PathContainer& archives, bool& writeDebugLog, bool& quiet) { - bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF, KF and BSA/BA2 files + bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF, KF, BGEM/BGSM and BSA/BA2 files Usages: - niftest + niftest Scan the file or directories for NIF errors. Allowed options)"); @@ -225,6 +267,10 @@ int main(int argc, char** argv) { readNIF({}, path, vfs.get(), quiet); } + else if (isMaterial(path)) + { + readMaterial({}, path, vfs.get(), quiet); + } else if (auto archive = makeArchive(path)) { readVFS(std::move(archive), path, quiet); From 124df1be6115a651308c6f310e913aa53c552302 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 01:51:31 +0300 Subject: [PATCH 419/451] Parse shared part of material files --- components/bgsm/file.cpp | 34 ++++++++++++++++++++++++++++++++++ components/bgsm/file.hpp | 6 +++--- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp index 870d9e4067..1ad1918ccf 100644 --- a/components/bgsm/file.cpp +++ b/components/bgsm/file.cpp @@ -6,6 +6,40 @@ namespace Bgsm { void MaterialFile::read(BGSMStream& stream) { + stream.read(mVersion); + stream.read(mClamp); + stream.read(mUVOffset); + stream.read(mUVScale); + stream.read(mTransparency); + stream.read(mAlphaBlend); + stream.read(mSourceBlendMode); + stream.read(mDestinationBlendMode); + stream.read(mAlphaTestThreshold); + stream.read(mAlphaTest); + stream.read(mDepthWrite); + stream.read(mSSR); + stream.read(mWetnessControlSSR); + stream.read(mDecal); + stream.read(mTwoSided); + stream.read(mDecalNoFade); + stream.read(mNonOccluder); + stream.read(mRefraction); + stream.read(mRefractionFalloff); + stream.read(mRefractionPower); + if (mVersion < 10) + { + stream.read(mEnvMap); + stream.read(mEnvMapMaskScale); + } + else + { + stream.read(mDepthBias); + } + stream.read(mGrayscaleToPaletteColor); + if (mVersion >= 6) + { + stream.read(mMaskWrites); + } } void BGSMFile::read(BGSMStream& stream) diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp index 117b135e4f..db0059cd29 100644 --- a/components/bgsm/file.hpp +++ b/components/bgsm/file.hpp @@ -27,10 +27,10 @@ namespace Bgsm std::uint32_t mClamp; osg::Vec2f mUVOffset, mUVScale; float mTransparency; - std::uint8_t mSourceBlendMode; + bool mAlphaBlend; + std::uint32_t mSourceBlendMode; std::uint32_t mDestinationBlendMode; - std::uint32_t mAlphaTestMode; - std::uint8_t mAlphaTestRef; + std::uint8_t mAlphaTestThreshold; bool mAlphaTest; bool mDepthWrite, mDepthTest; bool mSSR; From 015aca2cfd986bc048282828e246e042506ce585 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 02:28:48 +0300 Subject: [PATCH 420/451] Initial BGSM file parsing --- components/bgsm/file.cpp | 124 +++++++++++++++++++++++++++++++++++++++ components/bgsm/file.hpp | 6 +- 2 files changed, 127 insertions(+), 3 deletions(-) diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp index 1ad1918ccf..bb627ae34e 100644 --- a/components/bgsm/file.cpp +++ b/components/bgsm/file.cpp @@ -45,6 +45,130 @@ namespace Bgsm void BGSMFile::read(BGSMStream& stream) { MaterialFile::read(stream); + + stream.read(mDiffuseMap); + stream.read(mNormalMap); + stream.read(mSmoothSpecMap); + stream.read(mGreyscaleMap); + if (mVersion >= 3) + { + stream.read(mGlowMap); + stream.read(mWrinkleMap); + stream.read(mSpecularMap); + stream.read(mLightingMap); + stream.read(mFlowMap); + if (mVersion >= 17) + { + stream.read(mDistanceFieldAlphaMap); + } + } + else + { + stream.read(mEnvMap); + stream.read(mGlowMap); + stream.read(mInnerLayerMap); + stream.read(mWrinkleMap); + stream.read(mDisplacementMap); + } + stream.read(mEnableEditorAlphaThreshold); + if (mVersion >= 8) + { + stream.read(mTranslucency); + stream.read(mTranslucencyThickObject); + stream.read(mTranslucencyMixAlbedoWithSubsurfaceColor); + stream.read(mTranslucencySubsurfaceColor); + stream.read(mTranslucencyTransmissiveScale); + stream.read(mTranslucencyTurbulence); + } + else + { + stream.read(mRimLighting); + stream.read(mRimPower); + stream.read(mBackLightPower); + stream.read(mSubsurfaceLighting); + stream.read(mSubsurfaceLightingRolloff); + } + stream.read(mSpecularEnabled); + stream.read(mSpecularColor); + stream.read(mSpecularMult); + stream.read(mSmoothness); + stream.read(mFresnelPower); + stream.read(mWetnessControlSpecScale); + stream.read(mWetnessControlSpecPowerScale); + stream.read(mWetnessControlSpecMinvar); + if (mVersion < 10) + { + stream.read(mWetnessControlEnvMapScale); + } + stream.read(mWetnessControlFresnelPower); + stream.read(mWetnessControlMetalness); + if (mVersion >= 3) + { + stream.read(mPBR); + if (mVersion >= 9) + { + stream.read(mCustomPorosity); + stream.read(mPorosityValue); + } + } + stream.read(mRootMaterialPath); + stream.read(mAnisoLighting); + stream.read(mEmitEnabled); + if (mEmitEnabled) + { + stream.read(mEmittanceColor); + } + stream.read(mEmittanceMult); + stream.read(mModelSpaceNormals); + stream.read(mExternalEmittance); + if (mVersion >= 12) + { + stream.read(mLumEmittance); + if (mVersion >= 13) + { + stream.read(mUseAdaptiveEmissive); + stream.read(mAdaptiveEmissiveExposureParams); + } + } + else if (mVersion < 8) + { + stream.read(mBackLighting); + } + stream.read(mReceiveShadows); + stream.read(mHideSecret); + stream.read(mCastShadows); + stream.read(mDissolveFade); + stream.read(mAssumeShadowmask); + stream.read(mHasGlowMap); + if (mVersion < 7) + { + stream.read(mEnvMapWindow); + stream.read(mEnvMapEye); + } + stream.read(mHair); + stream.read(mHairTintColor); + stream.read(mTree); + stream.read(mFacegen); + stream.read(mSkinTint); + stream.read(mTessellate); + if (mVersion < 3) + { + stream.read(mDisplacementMapParams); + stream.read(mTessellationParams); + } + stream.read(mGrayscaleToPaletteScale); + if (mVersion >= 1) + { + stream.read(mSkewSpecularAlpha); + stream.read(mTerrain); + if (mTerrain) + { + if (mVersion == 3) + stream.skip(4); // Unknown + + stream.read(mTerrainParams); + } + } } void BGEMFile::read(BGSMStream& stream) diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp index db0059cd29..57b08f68dc 100644 --- a/components/bgsm/file.hpp +++ b/components/bgsm/file.hpp @@ -68,7 +68,7 @@ namespace Bgsm std::string mEnvMap; std::string mInnerLayerMap; std::string mDisplacementMap; - bool mEnableEditorAlphaRef; + bool mEnableEditorAlphaThreshold; bool mTranslucency; bool mTranslucencyThickObject; bool mTranslucencyMixAlbedoWithSubsurfaceColor; @@ -78,7 +78,7 @@ namespace Bgsm bool mRimLighting; float mRimPower; float mBackLightPower; - bool mSursurfaceLighting; + bool mSubsurfaceLighting; float mSubsurfaceLightingRolloff; bool mSpecularEnabled; osg::Vec4f mSpecularColor; @@ -120,7 +120,7 @@ namespace Bgsm bool mSkinTint; bool mTessellate; osg::Vec2f mDisplacementMapParams; - osg::Vec3f mTesselationParams; + osg::Vec3f mTessellationParams; float mGrayscaleToPaletteScale; bool mSkewSpecularAlpha; bool mTerrain; From 8ef6304dd9ea205cedfff2cc837e9a92efa5f3b6 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 02:33:30 +0300 Subject: [PATCH 421/451] BGSM colors are Vec3 --- components/bgsm/file.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp index 57b08f68dc..00e913917d 100644 --- a/components/bgsm/file.hpp +++ b/components/bgsm/file.hpp @@ -72,7 +72,7 @@ namespace Bgsm bool mTranslucency; bool mTranslucencyThickObject; bool mTranslucencyMixAlbedoWithSubsurfaceColor; - osg::Vec4f mTranslucencySubsurfaceColor; + osg::Vec3f mTranslucencySubsurfaceColor; float mTranslucencyTransmissiveScale; float mTranslucencyTurbulence; bool mRimLighting; @@ -81,7 +81,7 @@ namespace Bgsm bool mSubsurfaceLighting; float mSubsurfaceLightingRolloff; bool mSpecularEnabled; - osg::Vec4f mSpecularColor; + osg::Vec3f mSpecularColor; float mSpecularMult; float mSmoothness; float mFresnelPower; @@ -97,7 +97,7 @@ namespace Bgsm std::string mRootMaterialPath; bool mAnisoLighting; bool mEmitEnabled; - osg::Vec4f mEmittanceColor; + osg::Vec3f mEmittanceColor; float mEmittanceMult; bool mModelSpaceNormals; bool mExternalEmittance; @@ -114,7 +114,7 @@ namespace Bgsm bool mEnvMapWindow; bool mEnvMapEye; bool mHair; - osg::Vec4f mHairTintColor; + osg::Vec3f mHairTintColor; bool mTree; bool mFacegen; bool mSkinTint; @@ -145,13 +145,13 @@ namespace Bgsm bool mFalloffColor; bool mGrayscaleToPaletteAlpha; bool mSoft; - osg::Vec4f mBaseColor; + osg::Vec3f mBaseColor; float mBaseColorScale; osg::Vec4f mFalloffParams; float mLightingInfluence; std::uint8_t mEnvmapMinLOD; float mSoftDepth; - osg::Vec4f mEmittanceColor; + osg::Vec3f mEmittanceColor; osg::Vec3f mAdaptiveEmissiveExposureParams; bool mHasGlowMap; bool mEffectPbrSpecular; From f9f8c1e5912bdd3461e267f12cf4fa3a033d3094 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 02:57:32 +0300 Subject: [PATCH 422/451] Fix depth test reading in BGSM --- components/bgsm/file.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp index bb627ae34e..63b2df1d12 100644 --- a/components/bgsm/file.cpp +++ b/components/bgsm/file.cpp @@ -17,6 +17,7 @@ namespace Bgsm stream.read(mAlphaTestThreshold); stream.read(mAlphaTest); stream.read(mDepthWrite); + stream.read(mDepthTest); stream.read(mSSR); stream.read(mWetnessControlSSR); stream.read(mDecal); From 484a360792f42b696baaacd4d6711c204b1ace50 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 02:58:19 +0300 Subject: [PATCH 423/451] Add a safety measure for string loading in BGSM --- components/bgsm/stream.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/bgsm/stream.cpp b/components/bgsm/stream.cpp index d8434fcb8a..00cc382d3f 100644 --- a/components/bgsm/stream.cpp +++ b/components/bgsm/stream.cpp @@ -41,6 +41,9 @@ namespace Bgsm std::string BGSMStream::getSizedString(size_t length) { + // Prevent potential memory allocation freezes; strings this long are not expected in BGSM + if (length > 1024) + throw std::runtime_error("Requested string length is too large: " + std::to_string(length)); std::string str(length, '\0'); mStream->read(str.data(), length); if (mStream->bad()) From cb77bcc4c8797a76e33770b53046f8035b2c9d97 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 03:27:42 +0300 Subject: [PATCH 424/451] Initial BGEM file parsing --- components/bgsm/file.cpp | 51 +++++++++++++++++++++++++++++++++++++--- components/bgsm/file.hpp | 8 +++---- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp index 63b2df1d12..f330d8a84e 100644 --- a/components/bgsm/file.cpp +++ b/components/bgsm/file.cpp @@ -29,7 +29,7 @@ namespace Bgsm stream.read(mRefractionPower); if (mVersion < 10) { - stream.read(mEnvMap); + stream.read(mEnvMapEnabled); stream.read(mEnvMapMaskScale); } else @@ -50,7 +50,7 @@ namespace Bgsm stream.read(mDiffuseMap); stream.read(mNormalMap); stream.read(mSmoothSpecMap); - stream.read(mGreyscaleMap); + stream.read(mGrayscaleMap); if (mVersion >= 3) { stream.read(mGlowMap); @@ -140,7 +140,7 @@ namespace Bgsm stream.read(mCastShadows); stream.read(mDissolveFade); stream.read(mAssumeShadowmask); - stream.read(mHasGlowMap); + stream.read(mGlowMapEnabled); if (mVersion < 7) { stream.read(mEnvMapWindow); @@ -175,5 +175,50 @@ namespace Bgsm void BGEMFile::read(BGSMStream& stream) { MaterialFile::read(stream); + + stream.read(mBaseMap); + stream.read(mGrayscaleMap); + stream.read(mEnvMap); + stream.read(mNormalMap); + stream.read(mEnvMapMask); + if (mVersion >= 10) + { + if (mVersion >= 11) + { + stream.read(mSpecularMap); + stream.read(mLightingMap); + stream.read(mGlowMap); + } + stream.read(mEnvMapEnabled); + stream.read(mEnvMapMaskScale); + } + stream.read(mBlood); + stream.read(mEffectLighting); + stream.read(mFalloff); + stream.read(mFalloffColor); + stream.read(mGrayscaleToPaletteAlpha); + stream.read(mSoft); + stream.read(mBaseColor); + stream.read(mBaseColorScale); + stream.read(mFalloffParams); + stream.read(mLightingInfluence); + stream.read(mEnvmapMinLOD); + stream.read(mSoftDepth); + if (mVersion >= 11) + { + stream.read(mEmittanceColor); + if (mVersion >= 15) + { + stream.read(mAdaptiveEmissiveExposureParams); + if (mVersion >= 16) + { + stream.read(mGlowMapEnabled); + if (mVersion >= 20) + { + stream.read(mEffectPbrSpecular); + } + } + } + } } } diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp index 00e913917d..3524297e2d 100644 --- a/components/bgsm/file.hpp +++ b/components/bgsm/file.hpp @@ -42,7 +42,7 @@ namespace Bgsm bool mRefraction; bool mRefractionFalloff; float mRefractionPower; - bool mEnvMap; + bool mEnvMapEnabled; float mEnvMapMaskScale; bool mDepthBias; bool mGrayscaleToPaletteColor; @@ -58,7 +58,7 @@ namespace Bgsm std::string mDiffuseMap; std::string mNormalMap; std::string mSmoothSpecMap; - std::string mGreyscaleMap; + std::string mGrayscaleMap; std::string mGlowMap; std::string mWrinkleMap; std::string mSpecularMap; @@ -110,7 +110,7 @@ namespace Bgsm bool mCastShadows; bool mDissolveFade; bool mAssumeShadowmask; - bool mHasGlowMap; + bool mGlowMapEnabled; bool mEnvMapWindow; bool mEnvMapEye; bool mHair; @@ -153,7 +153,7 @@ namespace Bgsm float mSoftDepth; osg::Vec3f mEmittanceColor; osg::Vec3f mAdaptiveEmissiveExposureParams; - bool mHasGlowMap; + bool mGlowMapEnabled; bool mEffectPbrSpecular; void read(BGSMStream& stream) override; From fe1cb3a5aeb3da82c5fb11478f859d2014a211e7 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 04:00:20 +0300 Subject: [PATCH 425/451] Add a resource manager for BGSM files --- components/CMakeLists.txt | 2 +- components/bgsm/reader.hpp | 2 + components/resource/bgsmfilemanager.cpp | 62 +++++++++++++++++++++++++ components/resource/bgsmfilemanager.hpp | 27 +++++++++++ components/resource/stats.cpp | 1 + 5 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 components/resource/bgsmfilemanager.cpp create mode 100644 components/resource/bgsmfilemanager.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 084deaea58..df23121c5c 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -129,7 +129,7 @@ add_component_dir (vfs add_component_dir (resource scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem - resourcemanager stats animation foreachbulletobject errormarker cachestats + resourcemanager stats animation foreachbulletobject errormarker cachestats bgsmfilemanager ) add_component_dir (shader diff --git a/components/bgsm/reader.hpp b/components/bgsm/reader.hpp index 5ac67c0467..2d669900ad 100644 --- a/components/bgsm/reader.hpp +++ b/components/bgsm/reader.hpp @@ -20,6 +20,8 @@ namespace Bgsm void parse(Files::IStreamPtr&& stream); std::uint32_t getVersion() const { return mFile->mVersion; } + + std::unique_ptr& getFile() { return mFile; } }; } #endif diff --git a/components/resource/bgsmfilemanager.cpp b/components/resource/bgsmfilemanager.cpp new file mode 100644 index 0000000000..5155db17ce --- /dev/null +++ b/components/resource/bgsmfilemanager.cpp @@ -0,0 +1,62 @@ +#include "bgsmfilemanager.hpp" + +#include + +#include + +#include +#include + +#include "objectcache.hpp" + +namespace Resource +{ + + class BgsmFileHolder : public osg::Object + { + public: + BgsmFileHolder(const Bgsm::MaterialFilePtr& file) + : mBgsmFile(file) + { + } + BgsmFileHolder(const BgsmFileHolder& copy, const osg::CopyOp& copyop) + : mBgsmFile(copy.mBgsmFile) + { + } + + BgsmFileHolder() = default; + + META_Object(Resource, BgsmFileHolder) + + Bgsm::MaterialFilePtr mBgsmFile; + }; + + BgsmFileManager::BgsmFileManager(const VFS::Manager* vfs, double expiryDelay) + : ResourceManager(vfs, expiryDelay) + { + } + + BgsmFileManager::~BgsmFileManager() = default; + + Bgsm::MaterialFilePtr BgsmFileManager::get(VFS::Path::NormalizedView name) + { + osg::ref_ptr obj = mCache->getRefFromObjectCache(name); + if (obj) + return static_cast(obj.get())->mBgsmFile; + else + { + Bgsm::Reader reader; + reader.parse(mVFS->get(name)); + Bgsm::MaterialFilePtr file = std::move(reader.getFile()); + obj = new BgsmFileHolder(file); + mCache->addEntryToObjectCache(name.value(), obj); + return file; + } + } + + void BgsmFileManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const + { + Resource::reportStats("BSShader Material", frameNumber, mCache->getStats(), *stats); + } + +} diff --git a/components/resource/bgsmfilemanager.hpp b/components/resource/bgsmfilemanager.hpp new file mode 100644 index 0000000000..b7c0d07c5a --- /dev/null +++ b/components/resource/bgsmfilemanager.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_BGSMFILEMANAGER_H +#define OPENMW_COMPONENTS_RESOURCE_BGSMFILEMANAGER_H + +#include + +#include "resourcemanager.hpp" + +namespace Resource +{ + + /// @brief Handles caching of material files. + /// @note May be used from any thread. + class BgsmFileManager : public ResourceManager + { + public: + BgsmFileManager(const VFS::Manager* vfs, double expiryDelay); + ~BgsmFileManager(); + + /// Retrieve a material file from the cache or load it from the VFS if not cached yet. + Bgsm::MaterialFilePtr get(VFS::Path::NormalizedView name); + + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; + }; + +} + +#endif diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 9bb90635d1..6730ddb303 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -87,6 +87,7 @@ namespace Resource "Image", "Nif", "Keyframe", + "BSShader Material", "Groundcover Chunk", "Object Chunk", "Terrain Chunk", From 1a961f30210a6947f8ab39cbacfad042e6259ecd Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 05:01:12 +0300 Subject: [PATCH 426/451] Extremely early handling for BGSM/BGEM files --- apps/bulletobjecttool/main.cpp | 4 +- apps/navmeshtool/main.cpp | 4 +- .../nifosg/testnifloader.cpp | 6 +- components/misc/resourcehelpers.cpp | 5 + components/misc/resourcehelpers.hpp | 1 + components/nifosg/nifloader.cpp | 116 +++++++++++++++++- components/nifosg/nifloader.hpp | 3 +- components/resource/resourcesystem.cpp | 10 +- components/resource/resourcesystem.hpp | 3 + components/resource/scenemanager.cpp | 12 +- components/resource/scenemanager.hpp | 4 +- 11 files changed, 151 insertions(+), 17 deletions(-) diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index b27c8135d6..4dbdb56350 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -173,7 +174,8 @@ namespace constexpr double expiryDelay = 0; Resource::ImageManager imageManager(&vfs, expiryDelay); Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder()); - Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); + Resource::BgsmFileManager bgsmFileManager(&vfs, expiryDelay); + Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); Resource::forEachBulletObject( diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 94ab7ef082..d75a1af5e2 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -220,7 +221,8 @@ namespace NavMeshTool Resource::ImageManager imageManager(&vfs, expiryDelay); Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder()); - Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay); + Resource::BgsmFileManager bgsmFileManager(&vfs, expiryDelay); + Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay); Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay); DetourNavigator::RecastGlobalAllocator::init(); DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager(); diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index f05d651301..cdab51e6c2 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -70,7 +70,7 @@ namespace init(node); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager); + auto result = Loader::load(file, &mImageManager, nullptr); EXPECT_EQ(serialize(*result), R"( osg::Group { UniqueID 1 @@ -259,7 +259,7 @@ osg::Group { node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager); + auto result = Loader::load(file, &mImageManager, nullptr); EXPECT_EQ(serialize(*result), formatOsgNodeForBSShaderProperty(GetParam().mExpectedShaderPrefix)); } @@ -289,7 +289,7 @@ osg::Group { node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager); + auto result = Loader::load(file, &mImageManager, nullptr); EXPECT_EQ(serialize(*result), formatOsgNodeForBSLightingShaderProperty(GetParam().mExpectedShaderPrefix)); } diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 1d5b57bfd9..5c3f87b3e7 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -173,6 +173,11 @@ std::string Misc::ResourceHelpers::correctActorModelPath(const std::string& resP return mdlname; } +std::string Misc::ResourceHelpers::correctMaterialPath(std::string_view resPath, const VFS::Manager* vfs) +{ + return correctResourcePath({ { "materials" } }, resPath, vfs); +} + std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath) { std::string res = "meshes\\"; diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index cda99d928d..a2e05610a6 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -34,6 +34,7 @@ namespace Misc /// Use "xfoo.nif" instead of "foo.nif" if "xfoo.kf" is available /// Note that if "xfoo.nif" is actually unavailable, we can't fall back to "foo.nif". :( std::string correctActorModelPath(const std::string& resPath, const VFS::Manager* vfs); + std::string correctMaterialPath(std::string_view resPath, const VFS::Manager* vfs); // Adds "meshes\\". std::string correctMeshPath(std::string_view resPath); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 8d46b0f751..0150c2dc90 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include // particle @@ -42,6 +43,7 @@ #include #include +#include #include #include #include @@ -238,15 +240,17 @@ namespace NifOsg { public: /// @param filename used for warning messages. - LoaderImpl(const std::filesystem::path& filename, unsigned int ver, unsigned int userver, unsigned int bethver) + LoaderImpl(const std::filesystem::path& filename, unsigned int ver, unsigned int userver, unsigned int bethver, Resource::BgsmFileManager* materialMgr) : mFilename(filename) , mVersion(ver) , mUserVersion(userver) , mBethVersion(bethver) + , mMaterialMgr(materialMgr) { } std::filesystem::path mFilename; unsigned int mVersion, mUserVersion, mBethVersion; + Resource::BgsmFileManager* mMaterialMgr; size_t mFirstRootTextureIndex{ ~0u }; bool mFoundFirstRootTexturingProperty = false; @@ -2155,6 +2159,98 @@ namespace NifOsg handleTextureControllers(texprop, composite, imageManager, stateset, animflags); } + void handleShaderMaterial(const std::string& path, osg::StateSet* stateset, Resource::ImageManager* imageManager, + std::vector& boundTextures) + { + if (!mMaterialMgr) + return; + + Bgsm::MaterialFilePtr material = mMaterialMgr->get(VFS::Path::Normalized(path)); + if (!material) + return; + + if (material->mShaderType == Bgsm::ShaderType::Lighting) + { + const Bgsm::BGSMFile* bgsm = static_cast(material.get()); + + if (!boundTextures.empty()) + { + for (unsigned int i = 0; i < boundTextures.size(); ++i) + stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + + const unsigned int uvSet = 0; + if (!bgsm->mDiffuseMap.empty()) + { + std::string filename + = Misc::ResourceHelpers::correctTexturePath(bgsm->mDiffuseMap, imageManager->getVFS()); + osg::ref_ptr image = imageManager->getImage(filename); + osg::ref_ptr texture2d = new osg::Texture2D(image); + if (image) + texture2d->setTextureSize(image->s(), image->t()); + handleTextureWrapping(texture2d, (bgsm->mClamp >> 1) & 0x1, bgsm->mClamp & 0x1); + unsigned int texUnit = boundTextures.size(); + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + texture2d->setName("diffuseMap"); + boundTextures.emplace_back(uvSet); + } + + if (!bgsm->mNormalMap.empty()) + { + std::string filename + = Misc::ResourceHelpers::correctTexturePath(bgsm->mNormalMap, imageManager->getVFS()); + osg::ref_ptr image = imageManager->getImage(filename); + osg::ref_ptr texture2d = new osg::Texture2D(image); + if (image) + texture2d->setTextureSize(image->s(), image->t()); + handleTextureWrapping(texture2d, (bgsm->mClamp >> 1) & 0x1, bgsm->mClamp & 0x1); + unsigned int texUnit = boundTextures.size(); + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + texture2d->setName("normalMap"); + boundTextures.emplace_back(uvSet); + } + + if (bgsm->mTwoSided) + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + if (bgsm->mTree) + stateset->addUniform(new osg::Uniform("useTreeAnim", true)); + + handleDepthFlags(stateset, bgsm->mDepthTest, bgsm->mDepthWrite); + } + else + { + const Bgsm::BGEMFile* bgem = static_cast(material.get()); + if (!bgem->mBaseMap.empty()) + { + if (!boundTextures.empty()) + { + for (unsigned int i = 0; i < boundTextures.size(); ++i) + stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + std::string filename = Misc::ResourceHelpers::correctTexturePath( + bgem->mBaseMap, imageManager->getVFS()); + osg::ref_ptr image = imageManager->getImage(filename); + osg::ref_ptr texture2d = new osg::Texture2D(image); + texture2d->setName("diffuseMap"); + if (image) + texture2d->setTextureSize(image->s(), image->t()); + handleTextureWrapping(texture2d, (bgem->mClamp >> 1) & 0x1, bgem->mClamp & 0x1); + const unsigned int texUnit = 0; + const unsigned int uvSet = 0; + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + boundTextures.push_back(uvSet); + } + + bool useFalloff = bgem->mFalloff; + stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); + if (useFalloff) + stateset->addUniform(new osg::Uniform("falloffParams", bgem->mFalloffParams)); + handleDepthFlags(stateset, bgem->mDepthTest, bgem->mDepthWrite); + } + } + void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp, const std::string& nodeName, osg::StateSet* stateset, Resource::ImageManager* imageManager, std::vector& boundTextures) @@ -2421,6 +2517,12 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); + std::string normalizedName = Misc::ResourceHelpers::correctMaterialPath(texprop->mName, mMaterialMgr->getVFS()); + if (normalizedName.ends_with(".bgsm")) + { + handleShaderMaterial(normalizedName, stateset, imageManager, boundTextures); + break; + } if (!texprop->mTextureSet.empty()) handleTextureSet(texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); @@ -2442,6 +2544,12 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string("bs/nolighting")); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); + std::string normalizedName = Misc::ResourceHelpers::correctMaterialPath(texprop->mName, mMaterialMgr->getVFS()); + if (normalizedName.ends_with(".bgem")) + { + handleShaderMaterial(normalizedName, stateset, imageManager, boundTextures); + break; + } if (!texprop->mSourceTexture.empty()) { if (!boundTextures.empty()) @@ -2860,15 +2968,15 @@ namespace NifOsg } }; - osg::ref_ptr Loader::load(Nif::FileView file, Resource::ImageManager* imageManager) + osg::ref_ptr Loader::load(Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialMgr) { - LoaderImpl impl(file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion()); + LoaderImpl impl(file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion(), materialMgr); return impl.load(file, imageManager); } void Loader::loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target) { - LoaderImpl impl(kf.getFilename(), kf.getVersion(), kf.getUserVersion(), kf.getBethVersion()); + LoaderImpl impl(kf.getFilename(), kf.getVersion(), kf.getUserVersion(), kf.getBethVersion(), nullptr); impl.loadKf(kf, target); } diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index 21e0ae097c..b016248f07 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -18,6 +18,7 @@ namespace osg namespace Resource { class ImageManager; + class BgsmFileManager; } namespace NifOsg @@ -30,7 +31,7 @@ namespace NifOsg public: /// Create a scene graph for the given NIF. Auto-detects when skinning is used and wraps the graph in a Skeleton /// if so. - static osg::ref_ptr load(Nif::FileView file, Resource::ImageManager* imageManager); + static osg::ref_ptr load(Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager); /// Load keyframe controllers from the given kf file. static void loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target); diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 65a83a60ab..33bba791a8 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -2,6 +2,7 @@ #include +#include "bgsmfilemanager.hpp" #include "imagemanager.hpp" #include "keyframemanager.hpp" #include "niffilemanager.hpp" @@ -15,11 +16,13 @@ namespace Resource : mVFS(vfs) { mNifFileManager = std::make_unique(vfs, encoder); + mBgsmFileManager = std::make_unique(vfs, expiryDelay); mImageManager = std::make_unique(vfs, expiryDelay); - mSceneManager = std::make_unique(vfs, mImageManager.get(), mNifFileManager.get(), expiryDelay); + mSceneManager = std::make_unique(vfs, mImageManager.get(), mNifFileManager.get(), mBgsmFileManager.get(), expiryDelay); mKeyframeManager = std::make_unique(vfs, mSceneManager.get(), expiryDelay, encoder); addResourceManager(mNifFileManager.get()); + addResourceManager(mBgsmFileManager.get()); addResourceManager(mKeyframeManager.get()); // note, scene references images so add images afterwards for correct implementation of updateCache() addResourceManager(mSceneManager.get()); @@ -43,6 +46,11 @@ namespace Resource return mImageManager.get(); } + BgsmFileManager* ResourceSystem::getBgsmFileManager() + { + return mBgsmFileManager.get(); + } + NifFileManager* ResourceSystem::getNifFileManager() { return mNifFileManager.get(); diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index f7f09b9277..5609176a89 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -25,6 +25,7 @@ namespace Resource class SceneManager; class ImageManager; + class BgsmFileManager; class NifFileManager; class KeyframeManager; class BaseResourceManager; @@ -41,6 +42,7 @@ namespace Resource SceneManager* getSceneManager(); ImageManager* getImageManager(); + BgsmFileManager* getBgsmFileManager(); NifFileManager* getNifFileManager(); KeyframeManager* getKeyframeManager(); @@ -74,6 +76,7 @@ namespace Resource private: std::unique_ptr mSceneManager; std::unique_ptr mImageManager; + std::unique_ptr mBgsmFileManager; std::unique_ptr mNifFileManager; std::unique_ptr mKeyframeManager; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index ab3f92f10d..daeafeaf1d 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -55,6 +55,7 @@ #include #include +#include "bgsmfilemanager.hpp" #include "errormarker.hpp" #include "imagemanager.hpp" #include "niffilemanager.hpp" @@ -409,7 +410,7 @@ namespace Resource }; SceneManager::SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, - Resource::NifFileManager* nifFileManager, double expiryDelay) + Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay) : ResourceManager(vfs, expiryDelay) , mShaderManager(new Shader::ShaderManager) , mForceShaders(false) @@ -424,6 +425,7 @@ namespace Resource , mSharedStateManager(new SharedStateManager) , mImageManager(imageManager) , mNifFileManager(nifFileManager) + , mBgsmFileManager(bgsmFileManager) , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) , mMagFilter(osg::Texture::LINEAR) , mMaxAnisotropy(1) @@ -795,11 +797,11 @@ namespace Resource } osg::ref_ptr load(VFS::Path::NormalizedView normalizedFilename, const VFS::Manager* vfs, - Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) + Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* materialMgr) { const std::string_view ext = Misc::getFileExtension(normalizedFilename.value()); if (ext == "nif") - return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager); + return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager, materialMgr); else if (ext == "spt") { Log(Debug::Warning) << "Ignoring SpeedTree data file " << normalizedFilename; @@ -921,7 +923,7 @@ namespace Resource { path.changeExtension(meshType); if (mVFS->exists(path)) - return load(path, mVFS, mImageManager, mNifFileManager); + return load(path, mVFS, mImageManager, mNifFileManager, mBgsmFileManager); } } catch (const std::exception& e) @@ -953,7 +955,7 @@ namespace Resource osg::ref_ptr loaded; try { - loaded = load(normalized, mVFS, mImageManager, mNifFileManager); + loaded = load(normalized, mVFS, mImageManager, mNifFileManager, mBgsmFileManager); SceneUtil::ProcessExtraDataVisitor extraDataVisitor(this); loaded->accept(extraDataVisitor); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 3ad8a24892..31ad51694c 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -32,6 +32,7 @@ namespace Resource { class ImageManager; class NifFileManager; + class BgsmFileManager; class SharedStateManager; } @@ -90,7 +91,7 @@ namespace Resource { public: explicit SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager, - Resource::NifFileManager* nifFileManager, double expiryDelay); + Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay); ~SceneManager(); Shader::ShaderManager& getShaderManager(); @@ -259,6 +260,7 @@ namespace Resource Resource::ImageManager* mImageManager; Resource::NifFileManager* mNifFileManager; + Resource::BgsmFileManager* mBgsmFileManager; osg::Texture::FilterMode mMinFilter; osg::Texture::FilterMode mMagFilter; From 96f5ae5a8dae3afe522e9fd2ea5d30c9abb8a5b9 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 07:19:17 +0300 Subject: [PATCH 427/451] Handle BGSM decal flag, hide visibility editor markers --- components/nifosg/nifloader.cpp | 36 ++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 0150c2dc90..08a4fd5d89 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -773,7 +773,7 @@ namespace NifOsg if (isGeometry && !args.mSkipMeshes) { - bool skip; + bool skip = false; if (args.mNifVersion <= Nif::NIFFile::NIFVersion::VER_MW) { skip = (args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "tri editormarker")) @@ -781,7 +781,10 @@ namespace NifOsg || Misc::StringUtils::ciStartsWith(nifNode->mName, "tri shadow"); } else - skip = args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker"); + { + if (args.mHasMarkers) + skip = Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker") || Misc::StringUtils::ciStartsWith(nifNode->mName, "VisibilityEditorMarker"); + } if (!skip) { if (isNiGeometry) @@ -2165,7 +2168,8 @@ namespace NifOsg if (!mMaterialMgr) return; - Bgsm::MaterialFilePtr material = mMaterialMgr->get(VFS::Path::Normalized(path)); + std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, mMaterialMgr->getVFS()); + Bgsm::MaterialFilePtr material = mMaterialMgr->get(VFS::Path::Normalized(normalizedPath)); if (!material) return; @@ -2211,12 +2215,8 @@ namespace NifOsg boundTextures.emplace_back(uvSet); } - if (bgsm->mTwoSided) - stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); if (bgsm->mTree) stateset->addUniform(new osg::Uniform("useTreeAnim", true)); - - handleDepthFlags(stateset, bgsm->mDepthTest, bgsm->mDepthWrite); } else { @@ -2247,8 +2247,18 @@ namespace NifOsg stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); if (useFalloff) stateset->addUniform(new osg::Uniform("falloffParams", bgem->mFalloffParams)); - handleDepthFlags(stateset, bgem->mDepthTest, bgem->mDepthWrite); } + + if (material->mTwoSided) + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + if (material->mDecal) + { + osg::ref_ptr polygonOffset(new osg::PolygonOffset); + polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); + polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); + stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + } + handleDepthFlags(stateset, material->mDepthTest, material->mDepthWrite); } void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp, @@ -2517,10 +2527,9 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); - std::string normalizedName = Misc::ResourceHelpers::correctMaterialPath(texprop->mName, mMaterialMgr->getVFS()); - if (normalizedName.ends_with(".bgsm")) + if (Misc::StringUtils::ciEndsWith(texprop->mName, ".bgsm")) { - handleShaderMaterial(normalizedName, stateset, imageManager, boundTextures); + handleShaderMaterial(texprop->mName, stateset, imageManager, boundTextures); break; } if (!texprop->mTextureSet.empty()) @@ -2544,10 +2553,9 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string("bs/nolighting")); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); - std::string normalizedName = Misc::ResourceHelpers::correctMaterialPath(texprop->mName, mMaterialMgr->getVFS()); - if (normalizedName.ends_with(".bgem")) + if (Misc::StringUtils::ciEndsWith(texprop->mName, ".bgem")) { - handleShaderMaterial(normalizedName, stateset, imageManager, boundTextures); + handleShaderMaterial(texprop->mName, stateset, imageManager, boundTextures); break; } if (!texprop->mSourceTexture.empty()) From 1d65aaee714346bcff899f45a64abed2f48dda77 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 07:43:33 +0300 Subject: [PATCH 428/451] Formatting --- apps/niftest/niftest.cpp | 1 - components/bgsm/stream.hpp | 5 ++--- components/nifosg/nifloader.cpp | 20 ++++++++++++-------- components/nifosg/nifloader.hpp | 3 ++- components/resource/resourcesystem.cpp | 3 ++- components/resource/scenemanager.cpp | 3 ++- 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 3cd4382121..c364303376 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -89,7 +89,6 @@ void readNIF( } } - void readMaterial( const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) { diff --git a/components/bgsm/stream.hpp b/components/bgsm/stream.hpp index 8b0e1efdbe..2e03a52dd4 100644 --- a/components/bgsm/stream.hpp +++ b/components/bgsm/stream.hpp @@ -3,9 +3,9 @@ #include #include +#include #include #include -#include #include #include #include @@ -58,8 +58,7 @@ namespace Bgsm Files::IStreamPtr mStream; public: - explicit BGSMStream( - const Reader& reader, Files::IStreamPtr&& stream) + explicit BGSMStream(const Reader& reader, Files::IStreamPtr&& stream) : mReader(reader) , mStream(std::move(stream)) { diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 08a4fd5d89..657d053706 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -240,7 +240,8 @@ namespace NifOsg { public: /// @param filename used for warning messages. - LoaderImpl(const std::filesystem::path& filename, unsigned int ver, unsigned int userver, unsigned int bethver, Resource::BgsmFileManager* materialMgr) + LoaderImpl(const std::filesystem::path& filename, unsigned int ver, unsigned int userver, unsigned int bethver, + Resource::BgsmFileManager* materialMgr) : mFilename(filename) , mVersion(ver) , mUserVersion(userver) @@ -783,7 +784,8 @@ namespace NifOsg else { if (args.mHasMarkers) - skip = Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker") || Misc::StringUtils::ciStartsWith(nifNode->mName, "VisibilityEditorMarker"); + skip = Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker") + || Misc::StringUtils::ciStartsWith(nifNode->mName, "VisibilityEditorMarker"); } if (!skip) { @@ -2162,8 +2164,8 @@ namespace NifOsg handleTextureControllers(texprop, composite, imageManager, stateset, animflags); } - void handleShaderMaterial(const std::string& path, osg::StateSet* stateset, Resource::ImageManager* imageManager, - std::vector& boundTextures) + void handleShaderMaterial(const std::string& path, osg::StateSet* stateset, + Resource::ImageManager* imageManager, std::vector& boundTextures) { if (!mMaterialMgr) return; @@ -2229,8 +2231,8 @@ namespace NifOsg stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); boundTextures.clear(); } - std::string filename = Misc::ResourceHelpers::correctTexturePath( - bgem->mBaseMap, imageManager->getVFS()); + std::string filename + = Misc::ResourceHelpers::correctTexturePath(bgem->mBaseMap, imageManager->getVFS()); osg::ref_ptr image = imageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); texture2d->setName("diffuseMap"); @@ -2976,9 +2978,11 @@ namespace NifOsg } }; - osg::ref_ptr Loader::load(Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialMgr) + osg::ref_ptr Loader::load( + Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialMgr) { - LoaderImpl impl(file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion(), materialMgr); + LoaderImpl impl( + file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion(), materialMgr); return impl.load(file, imageManager); } diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index b016248f07..14f16088cc 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -31,7 +31,8 @@ namespace NifOsg public: /// Create a scene graph for the given NIF. Auto-detects when skinning is used and wraps the graph in a Skeleton /// if so. - static osg::ref_ptr load(Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager); + static osg::ref_ptr load( + Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager); /// Load keyframe controllers from the given kf file. static void loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target); diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 33bba791a8..f012627efb 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -18,7 +18,8 @@ namespace Resource mNifFileManager = std::make_unique(vfs, encoder); mBgsmFileManager = std::make_unique(vfs, expiryDelay); mImageManager = std::make_unique(vfs, expiryDelay); - mSceneManager = std::make_unique(vfs, mImageManager.get(), mNifFileManager.get(), mBgsmFileManager.get(), expiryDelay); + mSceneManager = std::make_unique( + vfs, mImageManager.get(), mNifFileManager.get(), mBgsmFileManager.get(), expiryDelay); mKeyframeManager = std::make_unique(vfs, mSceneManager.get(), expiryDelay, encoder); addResourceManager(mNifFileManager.get()); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index daeafeaf1d..690e38bbf8 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -797,7 +797,8 @@ namespace Resource } osg::ref_ptr load(VFS::Path::NormalizedView normalizedFilename, const VFS::Manager* vfs, - Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* materialMgr) + Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager, + Resource::BgsmFileManager* materialMgr) { const std::string_view ext = Misc::getFileExtension(normalizedFilename.value()); if (ext == "nif") From 8997bd68544d6a7e2e62593f49d6df63591a52f7 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 18 Apr 2024 10:10:03 +0300 Subject: [PATCH 429/451] Apply shader material transparency parameters, get rid of unwanted shiny --- components/nifosg/nifloader.cpp | 122 +++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 17 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 657d053706..81aeb7a2fc 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2164,17 +2164,21 @@ namespace NifOsg handleTextureControllers(texprop, composite, imageManager, stateset, animflags); } - void handleShaderMaterial(const std::string& path, osg::StateSet* stateset, - Resource::ImageManager* imageManager, std::vector& boundTextures) + Bgsm::MaterialFilePtr getShaderMaterial(const std::string& path) { if (!mMaterialMgr) - return; + return nullptr; + + if (!Misc::StringUtils::ciEndsWith(path, ".bgem") && !Misc::StringUtils::ciEndsWith(path, ".bgsm")) + return nullptr; std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, mMaterialMgr->getVFS()); - Bgsm::MaterialFilePtr material = mMaterialMgr->get(VFS::Path::Normalized(normalizedPath)); - if (!material) - return; + return mMaterialMgr->get(VFS::Path::Normalized(normalizedPath)); + } + void handleShaderMaterial(Bgsm::MaterialFilePtr material, osg::StateSet* stateset, + Resource::ImageManager* imageManager, std::vector& boundTextures) + { if (material->mShaderType == Bgsm::ShaderType::Lighting) { const Bgsm::BGSMFile* bgsm = static_cast(material.get()); @@ -2253,13 +2257,6 @@ namespace NifOsg if (material->mTwoSided) stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); - if (material->mDecal) - { - osg::ref_ptr polygonOffset(new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); - polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); - stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); - } handleDepthFlags(stateset, material->mDepthTest, material->mDepthWrite); } @@ -2529,9 +2526,10 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); - if (Misc::StringUtils::ciEndsWith(texprop->mName, ".bgsm")) + Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName); + if (material) { - handleShaderMaterial(texprop->mName, stateset, imageManager, boundTextures); + handleShaderMaterial(material, stateset, imageManager, boundTextures); break; } if (!texprop->mTextureSet.empty()) @@ -2555,9 +2553,10 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string("bs/nolighting")); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); - if (Misc::StringUtils::ciEndsWith(texprop->mName, ".bgem")) + Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName); + if (material) { - handleShaderMaterial(texprop->mName, stateset, imageManager, boundTextures); + handleShaderMaterial(material, stateset, imageManager, boundTextures); break; } if (!texprop->mSourceTexture.empty()) @@ -2832,6 +2831,52 @@ namespace NifOsg case Nif::RC_BSLightingShaderProperty: { auto shaderprop = static_cast(property); + Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName); + if (shaderMat) + { + mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency); + if (shaderMat->mAlphaTest) + { + osg::StateSet* stateset = node->getOrCreateStateSet(); + osg::ref_ptr alphaFunc(new osg::AlphaFunc( + osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold / 255.f)); + alphaFunc = shareAttribute(alphaFunc); + stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + } + if (shaderMat->mAlphaBlend) + { + osg::StateSet* stateset = node->getOrCreateStateSet(); + osg::ref_ptr blendFunc( + new osg::BlendFunc(getBlendMode(shaderMat->mSourceBlendMode), + getBlendMode(shaderMat->mDestinationBlendMode))); + blendFunc = shareAttribute(blendFunc); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + hasSortAlpha = true; + if (!mPushedSorter) + setBin_Transparent(stateset); + } + if (shaderMat->mDecal) + { + osg::StateSet* stateset = node->getOrCreateStateSet(); + if (!mPushedSorter && !hasSortAlpha) + stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); + osg::ref_ptr polygonOffset(new osg::PolygonOffset); + polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); + polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); + stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + } + if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) + { + auto bgsm = static_cast(shaderMat.get()); + specEnabled + = false; // bgsm->mSpecularEnabled; disabled until it can be implemented properly + specStrength = bgsm->mSpecularMult; + emissiveMult = bgsm->mEmittanceMult; + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mEmittanceColor, 1.f)); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mSpecularColor, 1.f)); + } + break; + } mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderprop->mAlpha); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mEmissive, 1.f)); mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mSpecular, 1.f)); @@ -2855,6 +2900,49 @@ namespace NifOsg case Nif::RC_BSEffectShaderProperty: { auto shaderprop = static_cast(property); + Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName); + if (shaderMat) + { + mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency); + if (shaderMat->mAlphaTest) + { + osg::StateSet* stateset = node->getOrCreateStateSet(); + osg::ref_ptr alphaFunc(new osg::AlphaFunc( + osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold / 255.f)); + alphaFunc = shareAttribute(alphaFunc); + stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + } + if (shaderMat->mAlphaBlend) + { + osg::StateSet* stateset = node->getOrCreateStateSet(); + osg::ref_ptr blendFunc( + new osg::BlendFunc(getBlendMode(shaderMat->mSourceBlendMode), + getBlendMode(shaderMat->mDestinationBlendMode))); + blendFunc = shareAttribute(blendFunc); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + hasSortAlpha = true; + if (!mPushedSorter) + setBin_Transparent(stateset); + } + if (shaderMat->mDecal) + { + osg::StateSet* stateset = node->getOrCreateStateSet(); + if (!mPushedSorter && !hasSortAlpha) + stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); + osg::ref_ptr polygonOffset(new osg::PolygonOffset); + polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); + polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); + stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + } + if (shaderMat->mShaderType == Bgsm::ShaderType::Effect) + { + auto bgem = static_cast(shaderMat.get()); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgem->mEmittanceColor, 1.f)); + if (bgem->mSoft) + SceneUtil::setupSoftEffect(*node, bgem->mSoftDepth, true, bgem->mSoftDepth); + } + break; + } if (shaderprop->decal()) { osg::StateSet* stateset = node->getOrCreateStateSet(); From e680123482190419db2ffa54315ea2a8479ae7d1 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 19 Apr 2024 13:35:29 +0300 Subject: [PATCH 430/451] NifLoader: Make the image manager a member --- components/nifosg/nifloader.cpp | 125 +++++++++++++++----------------- 1 file changed, 57 insertions(+), 68 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 81aeb7a2fc..6f630b4dd5 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -240,18 +240,17 @@ namespace NifOsg { public: /// @param filename used for warning messages. - LoaderImpl(const std::filesystem::path& filename, unsigned int ver, unsigned int userver, unsigned int bethver, - Resource::BgsmFileManager* materialMgr) + LoaderImpl(const std::filesystem::path& filename, unsigned int ver, unsigned int userver, unsigned int bethver) : mFilename(filename) , mVersion(ver) , mUserVersion(userver) , mBethVersion(bethver) - , mMaterialMgr(materialMgr) { } std::filesystem::path mFilename; unsigned int mVersion, mUserVersion, mBethVersion; - Resource::BgsmFileManager* mMaterialMgr; + Resource::BgsmFileManager* mMaterialManager{ nullptr }; + Resource::ImageManager* mImageManager{ nullptr }; size_t mFirstRootTextureIndex{ ~0u }; bool mFoundFirstRootTexturingProperty = false; @@ -344,7 +343,6 @@ namespace NifOsg struct HandleNodeArgs { unsigned int mNifVersion; - Resource::ImageManager* mImageManager; SceneUtil::TextKeyMap* mTextKeys; std::vector mBoundTextures = {}; int mAnimFlags = 0; @@ -354,7 +352,7 @@ namespace NifOsg osg::Node* mRootNode = nullptr; }; - osg::ref_ptr load(Nif::FileView nif, Resource::ImageManager* imageManager) + osg::ref_ptr load(Nif::FileView nif) { const size_t numRoots = nif.numRoots(); std::vector roots; @@ -376,10 +374,8 @@ namespace NifOsg created->setDataVariance(osg::Object::STATIC); for (const Nif::NiAVObject* root : roots) { - auto node = handleNode(root, nullptr, nullptr, - { .mNifVersion = nif.getVersion(), - .mImageManager = imageManager, - .mTextKeys = &textkeys->mTextKeys }); + auto node = handleNode( + root, nullptr, nullptr, { .mNifVersion = nif.getVersion(), .mTextKeys = &textkeys->mTextKeys }); created->addChild(node); } if (mHasNightDayLabel) @@ -410,8 +406,7 @@ namespace NifOsg } void applyNodeProperties(const Nif::NiAVObject* nifNode, osg::Node* applyTo, - SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, - std::vector& boundTextures, int animflags) + SceneUtil::CompositeStateSetUpdater* composite, std::vector& boundTextures, int animflags) { bool hasStencilProperty = false; @@ -449,8 +444,7 @@ namespace NifOsg if (property.getPtr()->recIndex == mFirstRootTextureIndex) applyTo->setUserValue("overrideFx", 1); } - handleProperty(property.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, - hasStencilProperty); + handleProperty(property.getPtr(), applyTo, composite, boundTextures, animflags, hasStencilProperty); } } @@ -462,8 +456,7 @@ namespace NifOsg shaderprop = static_cast(nifNode)->mShaderProperty; if (!shaderprop.empty()) - handleProperty(shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, - hasStencilProperty); + handleProperty(shaderprop.getPtr(), applyTo, composite, boundTextures, animflags, hasStencilProperty); } static void setupController(const Nif::NiTimeController* ctrl, SceneUtil::Controller* toSetup, int animflags) @@ -527,8 +520,7 @@ namespace NifOsg sequenceNode->setMode(osg::Sequence::START); } - osg::ref_ptr handleSourceTexture( - const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager) + osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st) { if (!st) return nullptr; @@ -536,8 +528,8 @@ namespace NifOsg osg::ref_ptr image; if (st->mExternal) { - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->mFile, imageManager->getVFS()); - image = imageManager->getImage(filename); + std::string filename = Misc::ResourceHelpers::correctTexturePath(st->mFile, mImageManager->getVFS()); + image = mImageManager->getImage(filename); } else if (!st->mData.empty()) { @@ -552,7 +544,7 @@ namespace NifOsg texture->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); } - bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset, Resource::ImageManager* imageManager) + bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset) { if (nifNode->recType != Nif::RC_NiTextureEffect) { @@ -595,7 +587,7 @@ namespace NifOsg return false; } - osg::ref_ptr image(handleSourceTexture(textureEffect->mTexture.getPtr(), imageManager)); + osg::ref_ptr image(handleSourceTexture(textureEffect->mTexture.getPtr())); osg::ref_ptr texture2d(new osg::Texture2D(image)); if (image) texture2d->setTextureSize(image->s(), image->t()); @@ -766,7 +758,7 @@ namespace NifOsg osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; - applyNodeProperties(nifNode, node, composite, args.mImageManager, args.mBoundTextures, args.mAnimFlags); + applyNodeProperties(nifNode, node, composite, args.mBoundTextures, args.mAnimFlags); const bool isNiGeometry = isTypeNiGeometry(nifNode->recType); const bool isBSGeometry = isTypeBSGeometry(nifNode->recType); @@ -868,7 +860,7 @@ namespace NifOsg if (!effect.empty()) { osg::ref_ptr effectStateSet = new osg::StateSet; - if (handleEffect(effect.getPtr(), effectStateSet, args.mImageManager)) + if (handleEffect(effect.getPtr(), effectStateSet)) for (unsigned int i = 0; i < currentNode->getNumChildren(); ++i) currentNode->getChild(i)->getOrCreateStateSet()->merge(*effectStateSet); } @@ -1035,8 +1027,7 @@ namespace NifOsg } void handleTextureControllers(const Nif::NiProperty* texProperty, - SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, - osg::StateSet* stateset, int animflags) + SceneUtil::CompositeStateSetUpdater* composite, osg::StateSet* stateset, int animflags) { for (Nif::NiTimeControllerPtr ctrl = texProperty->mController; !ctrl.empty(); ctrl = ctrl->mNext) { @@ -1070,7 +1061,7 @@ namespace NifOsg if (source.empty()) continue; - osg::ref_ptr image(handleSourceTexture(source.getPtr(), imageManager)); + osg::ref_ptr image(handleSourceTexture(source.getPtr())); osg::ref_ptr texture(new osg::Texture2D(image)); if (image) texture->setTextureSize(image->s(), image->t()); @@ -1986,7 +1977,7 @@ namespace NifOsg void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, - Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) + std::vector& boundTextures, int animflags) { if (!boundTextures.empty()) { @@ -2039,7 +2030,7 @@ namespace NifOsg if (!tex.mSourceTexture.empty()) { const Nif::NiSourceTexture* st = tex.mSourceTexture.getPtr(); - osg::ref_ptr image = handleSourceTexture(st, imageManager); + osg::ref_ptr image = handleSourceTexture(st); texture2d = new osg::Texture2D(image); if (image) texture2d->setTextureSize(image->s(), image->t()); @@ -2161,23 +2152,23 @@ namespace NifOsg boundTextures.push_back(uvSet); } } - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleTextureControllers(texprop, composite, stateset, animflags); } Bgsm::MaterialFilePtr getShaderMaterial(const std::string& path) { - if (!mMaterialMgr) + if (!mMaterialManager) return nullptr; if (!Misc::StringUtils::ciEndsWith(path, ".bgem") && !Misc::StringUtils::ciEndsWith(path, ".bgsm")) return nullptr; - std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, mMaterialMgr->getVFS()); - return mMaterialMgr->get(VFS::Path::Normalized(normalizedPath)); + std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, mMaterialManager->getVFS()); + return mMaterialManager->get(VFS::Path::Normalized(normalizedPath)); } - void handleShaderMaterial(Bgsm::MaterialFilePtr material, osg::StateSet* stateset, - Resource::ImageManager* imageManager, std::vector& boundTextures) + void handleShaderMaterial( + Bgsm::MaterialFilePtr material, osg::StateSet* stateset, std::vector& boundTextures) { if (material->mShaderType == Bgsm::ShaderType::Lighting) { @@ -2194,8 +2185,8 @@ namespace NifOsg if (!bgsm->mDiffuseMap.empty()) { std::string filename - = Misc::ResourceHelpers::correctTexturePath(bgsm->mDiffuseMap, imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); + = Misc::ResourceHelpers::correctTexturePath(bgsm->mDiffuseMap, mImageManager->getVFS()); + osg::ref_ptr image = mImageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); if (image) texture2d->setTextureSize(image->s(), image->t()); @@ -2209,8 +2200,8 @@ namespace NifOsg if (!bgsm->mNormalMap.empty()) { std::string filename - = Misc::ResourceHelpers::correctTexturePath(bgsm->mNormalMap, imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); + = Misc::ResourceHelpers::correctTexturePath(bgsm->mNormalMap, mImageManager->getVFS()); + osg::ref_ptr image = mImageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); if (image) texture2d->setTextureSize(image->s(), image->t()); @@ -2236,8 +2227,8 @@ namespace NifOsg boundTextures.clear(); } std::string filename - = Misc::ResourceHelpers::correctTexturePath(bgem->mBaseMap, imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); + = Misc::ResourceHelpers::correctTexturePath(bgem->mBaseMap, mImageManager->getVFS()); + osg::ref_ptr image = mImageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); texture2d->setName("diffuseMap"); if (image) @@ -2261,8 +2252,7 @@ namespace NifOsg } void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp, - const std::string& nodeName, osg::StateSet* stateset, Resource::ImageManager* imageManager, - std::vector& boundTextures) + const std::string& nodeName, osg::StateSet* stateset, std::vector& boundTextures) { if (!boundTextures.empty()) { @@ -2291,8 +2281,8 @@ namespace NifOsg } } std::string filename - = Misc::ResourceHelpers::correctTexturePath(textureSet->mTextures[i], imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); + = Misc::ResourceHelpers::correctTexturePath(textureSet->mTextures[i], mImageManager->getVFS()); + osg::ref_ptr image = mImageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); if (image) texture2d->setTextureSize(image->s(), image->t()); @@ -2374,8 +2364,8 @@ namespace NifOsg } void handleProperty(const Nif::NiProperty* property, osg::Node* node, - SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, - std::vector& boundTextures, int animflags, bool hasStencilProperty) + SceneUtil::CompositeStateSetUpdater* composite, std::vector& boundTextures, int animflags, + bool hasStencilProperty) { switch (property->recType) { @@ -2457,8 +2447,7 @@ namespace NifOsg { const Nif::NiTexturingProperty* texprop = static_cast(property); osg::StateSet* stateset = node->getOrCreateStateSet(); - handleTextureProperty( - texprop, node->getName(), stateset, composite, imageManager, boundTextures, animflags); + handleTextureProperty(texprop, node->getName(), stateset, composite, boundTextures, animflags); node->setUserValue("applyMode", static_cast(texprop->mApplyMode)); break; } @@ -2472,10 +2461,9 @@ namespace NifOsg if (!texprop->mTextureSet.empty()) { auto textureSet = texprop->mTextureSet.getPtr(); - handleTextureSet( - textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); + handleTextureSet(textureSet, texprop->mClamp, node->getName(), stateset, boundTextures); } - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleTextureControllers(texprop, composite, stateset, animflags); if (texprop->refraction()) SceneUtil::setupDistortion(*node, texprop->mRefraction.mStrength); break; @@ -2497,8 +2485,8 @@ namespace NifOsg boundTextures.clear(); } std::string filename - = Misc::ResourceHelpers::correctTexturePath(texprop->mFilename, imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); + = Misc::ResourceHelpers::correctTexturePath(texprop->mFilename, mImageManager->getVFS()); + osg::ref_ptr image = mImageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); texture2d->setName("diffuseMap"); if (image) @@ -2515,7 +2503,7 @@ namespace NifOsg } } stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleTextureControllers(texprop, composite, stateset, animflags); handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } @@ -2529,13 +2517,13 @@ namespace NifOsg Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName); if (material) { - handleShaderMaterial(material, stateset, imageManager, boundTextures); + handleShaderMaterial(material, stateset, boundTextures); break; } if (!texprop->mTextureSet.empty()) - handleTextureSet(texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset, - imageManager, boundTextures); - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleTextureSet( + texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset, boundTextures); + handleTextureControllers(texprop, composite, stateset, animflags); if (texprop->doubleSided()) stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); if (texprop->treeAnim()) @@ -2556,7 +2544,7 @@ namespace NifOsg Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName); if (material) { - handleShaderMaterial(material, stateset, imageManager, boundTextures); + handleShaderMaterial(material, stateset, boundTextures); break; } if (!texprop->mSourceTexture.empty()) @@ -2568,8 +2556,8 @@ namespace NifOsg boundTextures.clear(); } std::string filename = Misc::ResourceHelpers::correctTexturePath( - texprop->mSourceTexture, imageManager->getVFS()); - osg::ref_ptr image = imageManager->getImage(filename); + texprop->mSourceTexture, mImageManager->getVFS()); + osg::ref_ptr image = mImageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); texture2d->setName("diffuseMap"); if (image) @@ -2601,7 +2589,7 @@ namespace NifOsg stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); if (useFalloff) stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams)); - handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleTextureControllers(texprop, composite, stateset, animflags); if (texprop->doubleSided()) stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); @@ -3067,16 +3055,17 @@ namespace NifOsg }; osg::ref_ptr Loader::load( - Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialMgr) + Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager) { - LoaderImpl impl( - file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion(), materialMgr); - return impl.load(file, imageManager); + LoaderImpl impl(file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion()); + impl.mMaterialManager = materialManager; + impl.mImageManager = imageManager; + return impl.load(file); } void Loader::loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target) { - LoaderImpl impl(kf.getFilename(), kf.getVersion(), kf.getUserVersion(), kf.getBethVersion(), nullptr); + LoaderImpl impl(kf.getFilename(), kf.getVersion(), kf.getUserVersion(), kf.getBethVersion()); impl.loadKf(kf, target); } From 4ccf9c1917c0ebc0d1b7219469a41d438c1475c4 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 19 Apr 2024 16:07:21 +0300 Subject: [PATCH 431/451] Deduplicate NifLoader texture attachment Handle non-existent shader materials more gracefully Deduplicate shader material drawable property handling --- components/nifosg/nifloader.cpp | 515 +++++++++++++------------------- 1 file changed, 202 insertions(+), 313 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 6f630b4dd5..7177377771 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -520,28 +520,18 @@ namespace NifOsg sequenceNode->setMode(osg::Sequence::START); } - osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st) + osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st) const { - if (!st) - return nullptr; - - osg::ref_ptr image; - if (st->mExternal) - { - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->mFile, mImageManager->getVFS()); - image = mImageManager->getImage(filename); - } - else if (!st->mData.empty()) + if (st) { - image = handleInternalTexture(st->mData.getPtr()); + if (st->mExternal) + return getTextureImage(st->mFile); + + if (!st->mData.empty()) + return handleInternalTexture(st->mData.getPtr()); } - return image; - } - void handleTextureWrapping(osg::Texture2D* texture, bool wrapS, bool wrapT) - { - texture->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); - texture->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + return nullptr; } bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset) @@ -587,16 +577,12 @@ namespace NifOsg return false; } - osg::ref_ptr image(handleSourceTexture(textureEffect->mTexture.getPtr())); - osg::ref_ptr texture2d(new osg::Texture2D(image)); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - texture2d->setName("envMap"); - handleTextureWrapping(texture2d, textureEffect->wrapS(), textureEffect->wrapT()); - - int texUnit = 3; // FIXME - - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + const unsigned int uvSet = 0; + const unsigned int texUnit = 3; // FIXME + std::vector boundTextures; + boundTextures.resize(3); // Dummy vector for attachNiSourceTexture + attachNiSourceTexture("envMap", textureEffect->mTexture.getPtr(), textureEffect->wrapS(), + textureEffect->wrapT(), uvSet, stateset, boundTextures); stateset->setTextureAttributeAndModes(texUnit, texGen, osg::StateAttribute::ON); stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON); @@ -1026,6 +1012,54 @@ namespace NifOsg } } + osg::ref_ptr getTextureImage(std::string_view path) const + { + if (!mImageManager) + return nullptr; + + std::string filename = Misc::ResourceHelpers::correctTexturePath(path, mImageManager->getVFS()); + return mImageManager->getImage(filename); + } + + osg::ref_ptr attachTexture(const std::string& name, osg::ref_ptr image, bool wrapS, + bool wrapT, unsigned int uvSet, osg::StateSet* stateset, std::vector& boundTextures) const + { + osg::ref_ptr texture2d = new osg::Texture2D(image); + if (image) + texture2d->setTextureSize(image->s(), image->t()); + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); + unsigned int texUnit = boundTextures.size(); + if (stateset) + stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); + texture2d->setName(name); + boundTextures.emplace_back(uvSet); + return texture2d; + } + + osg::ref_ptr attachExternalTexture(const std::string& name, const std::string& path, bool wrapS, + bool wrapT, unsigned int uvSet, osg::StateSet* stateset, std::vector& boundTextures) const + { + return attachTexture(name, getTextureImage(path), wrapS, wrapT, uvSet, stateset, boundTextures); + } + + osg::ref_ptr attachNiSourceTexture(const std::string& name, const Nif::NiSourceTexture* st, + bool wrapS, bool wrapT, unsigned int uvSet, osg::StateSet* stateset, + std::vector& boundTextures) const + { + return attachTexture(name, handleSourceTexture(st), wrapS, wrapT, uvSet, stateset, boundTextures); + } + + static void clearBoundTextures(osg::StateSet* stateset, std::vector& boundTextures) + { + if (!boundTextures.empty()) + { + for (unsigned int i = 0; i < boundTextures.size(); ++i) + stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); + boundTextures.clear(); + } + } + void handleTextureControllers(const Nif::NiProperty* texProperty, SceneUtil::CompositeStateSetUpdater* composite, osg::StateSet* stateset, int animflags) { @@ -1056,17 +1090,16 @@ namespace NifOsg wrapT = inherit->getWrap(osg::Texture2D::WRAP_T); } + const unsigned int uvSet = 0; + std::vector boundTextures; // Dummy list for attachTexture for (const auto& source : flipctrl->mSources) { if (source.empty()) continue; - osg::ref_ptr image(handleSourceTexture(source.getPtr())); - osg::ref_ptr texture(new osg::Texture2D(image)); - if (image) - texture->setTextureSize(image->s(), image->t()); - texture->setWrap(osg::Texture::WRAP_S, wrapS); - texture->setWrap(osg::Texture::WRAP_T, wrapT); + // NB: not changing the stateset + osg::ref_ptr texture + = attachNiSourceTexture({}, source.getPtr(), wrapS, wrapT, uvSet, nullptr, boundTextures); textures.push_back(texture); } osg::ref_ptr callback(new FlipController(flipctrl, textures)); @@ -1811,7 +1844,7 @@ namespace NifOsg } } - osg::ref_ptr handleInternalTexture(const Nif::NiPixelData* pixelData) + osg::ref_ptr handleInternalTexture(const Nif::NiPixelData* pixelData) const { if (pixelData->mMipmaps.empty()) return nullptr; @@ -1946,7 +1979,7 @@ namespace NifOsg return image; } - osg::ref_ptr createEmissiveTexEnv() + static osg::ref_ptr createEmissiveTexEnv() { osg::ref_ptr texEnv(new osg::TexEnvCombine); // Sum the previous colour and the emissive colour. @@ -1979,31 +2012,40 @@ namespace NifOsg osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, std::vector& boundTextures, int animflags) { - if (!boundTextures.empty()) - { - // overriding a parent NiTexturingProperty, so remove what was previously bound - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } + // overriding a parent NiTexturingProperty, so remove what was previously bound + clearBoundTextures(stateset, boundTextures); // If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the // shadow casting shader will need to be updated accordingly. for (size_t i = 0; i < texprop->mTextures.size(); ++i) { - if (texprop->mTextures[i].mEnabled - || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->mController.empty())) + const Nif::NiTexturingProperty::Texture& tex = texprop->mTextures[i]; + if (tex.mEnabled || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->mController.empty())) { + std::string textureName; switch (i) { // These are handled later on case Nif::NiTexturingProperty::BaseTexture: + textureName = "diffuseMap"; + break; case Nif::NiTexturingProperty::GlowTexture: + textureName = "glowMap"; + break; case Nif::NiTexturingProperty::DarkTexture: + textureName = "darkMap"; + break; case Nif::NiTexturingProperty::BumpTexture: + textureName = "bumpMap"; + break; case Nif::NiTexturingProperty::DetailTexture: + textureName = "detailMap"; + break; case Nif::NiTexturingProperty::DecalTexture: + textureName = "decalMap"; + break; case Nif::NiTexturingProperty::GlossTexture: + textureName = "glossMap"; break; default: { @@ -2013,12 +2055,9 @@ namespace NifOsg } } - unsigned int uvSet = 0; - // create a new texture, will later attempt to share using the SharedStateManager - osg::ref_ptr texture2d; - if (texprop->mTextures[i].mEnabled) + const unsigned int texUnit = boundTextures.size(); + if (tex.mEnabled) { - const Nif::NiTexturingProperty::Texture& tex = texprop->mTextures[i]; if (tex.mSourceTexture.empty() && texprop->mController.empty()) { if (i == 0) @@ -2028,32 +2067,18 @@ namespace NifOsg } if (!tex.mSourceTexture.empty()) - { - const Nif::NiSourceTexture* st = tex.mSourceTexture.getPtr(); - osg::ref_ptr image = handleSourceTexture(st); - texture2d = new osg::Texture2D(image); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - } + attachNiSourceTexture(textureName, tex.mSourceTexture.getPtr(), tex.wrapS(), tex.wrapT(), + tex.mUVSet, stateset, boundTextures); else - texture2d = new osg::Texture2D; - - handleTextureWrapping(texture2d, tex.wrapS(), tex.wrapT()); - - uvSet = tex.mUVSet; + attachTexture( + textureName, nullptr, tex.wrapS(), tex.wrapT(), tex.mUVSet, stateset, boundTextures); } else { // Texture only comes from NiFlipController, so tex is ignored, set defaults - texture2d = new osg::Texture2D; - handleTextureWrapping(texture2d, true, true); - uvSet = 0; + attachTexture(textureName, nullptr, true, true, 0, stateset, boundTextures); } - unsigned int texUnit = boundTextures.size(); - - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - if (i == Nif::NiTexturingProperty::GlowTexture) { stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON); @@ -2121,41 +2146,12 @@ namespace NifOsg texEnv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA); stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON); } - - switch (i) - { - case Nif::NiTexturingProperty::BaseTexture: - texture2d->setName("diffuseMap"); - break; - case Nif::NiTexturingProperty::BumpTexture: - texture2d->setName("bumpMap"); - break; - case Nif::NiTexturingProperty::GlowTexture: - texture2d->setName("emissiveMap"); - break; - case Nif::NiTexturingProperty::DarkTexture: - texture2d->setName("darkMap"); - break; - case Nif::NiTexturingProperty::DetailTexture: - texture2d->setName("detailMap"); - break; - case Nif::NiTexturingProperty::DecalTexture: - texture2d->setName("decalMap"); - break; - case Nif::NiTexturingProperty::GlossTexture: - texture2d->setName("glossMap"); - break; - default: - break; - } - - boundTextures.push_back(uvSet); } } handleTextureControllers(texprop, composite, stateset, animflags); } - Bgsm::MaterialFilePtr getShaderMaterial(const std::string& path) + Bgsm::MaterialFilePtr getShaderMaterial(std::string_view path) const { if (!mMaterialManager) return nullptr; @@ -2164,81 +2160,43 @@ namespace NifOsg return nullptr; std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, mMaterialManager->getVFS()); - return mMaterialManager->get(VFS::Path::Normalized(normalizedPath)); + try + { + return mMaterialManager->get(VFS::Path::Normalized(normalizedPath)); + } + catch (std::exception& e) + { + Log(Debug::Error) << "Failed to load shader material: " << e.what(); + return nullptr; + } } - void handleShaderMaterial( + void handleShaderMaterialNodeProperties( Bgsm::MaterialFilePtr material, osg::StateSet* stateset, std::vector& boundTextures) { + const unsigned int uvSet = 0; + const bool wrapS = (material->mClamp >> 1) & 0x1; + const bool wrapT = material->mClamp & 0x1; if (material->mShaderType == Bgsm::ShaderType::Lighting) { const Bgsm::BGSMFile* bgsm = static_cast(material.get()); - if (!boundTextures.empty()) - { - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } - - const unsigned int uvSet = 0; if (!bgsm->mDiffuseMap.empty()) - { - std::string filename - = Misc::ResourceHelpers::correctTexturePath(bgsm->mDiffuseMap, mImageManager->getVFS()); - osg::ref_ptr image = mImageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, (bgsm->mClamp >> 1) & 0x1, bgsm->mClamp & 0x1); - unsigned int texUnit = boundTextures.size(); - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - texture2d->setName("diffuseMap"); - boundTextures.emplace_back(uvSet); - } + attachExternalTexture( + "diffuseMap", bgsm->mDiffuseMap, wrapS, wrapT, uvSet, stateset, boundTextures); if (!bgsm->mNormalMap.empty()) - { - std::string filename - = Misc::ResourceHelpers::correctTexturePath(bgsm->mNormalMap, mImageManager->getVFS()); - osg::ref_ptr image = mImageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, (bgsm->mClamp >> 1) & 0x1, bgsm->mClamp & 0x1); - unsigned int texUnit = boundTextures.size(); - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - texture2d->setName("normalMap"); - boundTextures.emplace_back(uvSet); - } + attachExternalTexture("normalMap", bgsm->mNormalMap, wrapS, wrapT, uvSet, stateset, boundTextures); if (bgsm->mTree) stateset->addUniform(new osg::Uniform("useTreeAnim", true)); } - else + else if (material->mShaderType == Bgsm::ShaderType::Effect) { const Bgsm::BGEMFile* bgem = static_cast(material.get()); + if (!bgem->mBaseMap.empty()) - { - if (!boundTextures.empty()) - { - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } - std::string filename - = Misc::ResourceHelpers::correctTexturePath(bgem->mBaseMap, mImageManager->getVFS()); - osg::ref_ptr image = mImageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - texture2d->setName("diffuseMap"); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, (bgem->mClamp >> 1) & 0x1, bgem->mClamp & 0x1); - const unsigned int texUnit = 0; - const unsigned int uvSet = 0; - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - boundTextures.push_back(uvSet); - } + attachExternalTexture("diffuseMap", bgem->mBaseMap, wrapS, wrapT, uvSet, stateset, boundTextures); bool useFalloff = bgem->mFalloff; stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); @@ -2251,16 +2209,55 @@ namespace NifOsg handleDepthFlags(stateset, material->mDepthTest, material->mDepthWrite); } - void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp, - const std::string& nodeName, osg::StateSet* stateset, std::vector& boundTextures) + void handleShaderMaterialDrawableProperties( + Bgsm::MaterialFilePtr shaderMat, osg::ref_ptr mat, osg::Node& node, bool& hasSortAlpha) { - if (!boundTextures.empty()) + mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency); + if (shaderMat->mAlphaTest) { - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); + osg::StateSet* stateset = node.getOrCreateStateSet(); + osg::ref_ptr alphaFunc( + new osg::AlphaFunc(osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold / 255.f)); + alphaFunc = shareAttribute(alphaFunc); + stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + } + if (shaderMat->mAlphaBlend) + { + osg::StateSet* stateset = node.getOrCreateStateSet(); + osg::ref_ptr blendFunc(new osg::BlendFunc( + getBlendMode(shaderMat->mSourceBlendMode), getBlendMode(shaderMat->mDestinationBlendMode))); + blendFunc = shareAttribute(blendFunc); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + hasSortAlpha = true; + } + if (shaderMat->mDecal) + { + osg::StateSet* stateset = node.getOrCreateStateSet(); + if (!mPushedSorter && !hasSortAlpha) + stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); + osg::ref_ptr polygonOffset(new osg::PolygonOffset); + polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); + polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); + stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + } + if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) + { + auto bgsm = static_cast(shaderMat.get()); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mEmittanceColor, 1.f)); + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mSpecularColor, 1.f)); } + else if (shaderMat->mShaderType == Bgsm::ShaderType::Effect) + { + auto bgem = static_cast(shaderMat.get()); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgem->mEmittanceColor, 1.f)); + if (bgem->mSoft) + SceneUtil::setupSoftEffect(node, bgem->mSoftDepth, true, bgem->mSoftDepth); + } + } + void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, bool wrapS, bool wrapT, + const std::string& nodeName, osg::StateSet* stateset, std::vector& boundTextures) + { const unsigned int uvSet = 0; for (size_t i = 0; i < textureSet->mTextures.size(); ++i) @@ -2270,8 +2267,16 @@ namespace NifOsg switch (static_cast(i)) { case Nif::BSShaderTextureSet::TextureType::Base: + attachExternalTexture( + "diffuseMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures); + break; case Nif::BSShaderTextureSet::TextureType::Normal: + attachExternalTexture( + "normalMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures); + break; case Nif::BSShaderTextureSet::TextureType::Glow: + attachExternalTexture( + "emissiveMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures); break; default: { @@ -2280,31 +2285,6 @@ namespace NifOsg continue; } } - std::string filename - = Misc::ResourceHelpers::correctTexturePath(textureSet->mTextures[i], mImageManager->getVFS()); - osg::ref_ptr image = mImageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, (clamp >> 1) & 0x1, clamp & 0x1); - unsigned int texUnit = boundTextures.size(); - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - // BSShaderTextureSet presence means there's no need for FFP support for the affected node - switch (static_cast(i)) - { - case Nif::BSShaderTextureSet::TextureType::Base: - texture2d->setName("diffuseMap"); - break; - case Nif::BSShaderTextureSet::TextureType::Normal: - texture2d->setName("normalMap"); - break; - case Nif::BSShaderTextureSet::TextureType::Glow: - texture2d->setName("emissiveMap"); - break; - default: - break; - } - boundTextures.emplace_back(uvSet); } } @@ -2458,11 +2438,12 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); + clearBoundTextures(stateset, boundTextures); + const bool wrapS = (texprop->mClamp >> 1) & 0x1; + const bool wrapT = texprop->mClamp & 0x1; if (!texprop->mTextureSet.empty()) - { - auto textureSet = texprop->mTextureSet.getPtr(); - handleTextureSet(textureSet, texprop->mClamp, node->getName(), stateset, boundTextures); - } + handleTextureSet( + texprop->mTextureSet.getPtr(), wrapS, wrapT, node->getName(), stateset, boundTextures); handleTextureControllers(texprop, composite, stateset, animflags); if (texprop->refraction()) SceneUtil::setupDistortion(*node, texprop->mRefraction.mStrength); @@ -2476,31 +2457,17 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); + clearBoundTextures(stateset, boundTextures); if (!texprop->mFilename.empty()) { - if (!boundTextures.empty()) - { - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } - std::string filename - = Misc::ResourceHelpers::correctTexturePath(texprop->mFilename, mImageManager->getVFS()); - osg::ref_ptr image = mImageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - texture2d->setName("diffuseMap"); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, texprop->wrapS(), texprop->wrapT()); - const unsigned int texUnit = 0; const unsigned int uvSet = 0; - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - boundTextures.push_back(uvSet); - if (mBethVersion >= 27) - { - useFalloff = true; - stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams)); - } + attachExternalTexture("diffuseMap", texprop->mFilename, texprop->wrapS(), texprop->wrapT(), + uvSet, stateset, boundTextures); + } + if (mBethVersion >= 27) + { + useFalloff = true; + stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams)); } stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); handleTextureControllers(texprop, composite, stateset, animflags); @@ -2514,15 +2481,17 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->mType))); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); - Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName); - if (material) + clearBoundTextures(stateset, boundTextures); + if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName)) { - handleShaderMaterial(material, stateset, boundTextures); + handleShaderMaterialNodeProperties(material, stateset, boundTextures); break; } + const bool wrapS = (texprop->mClamp >> 1) & 0x1; + const bool wrapT = texprop->mClamp & 0x1; if (!texprop->mTextureSet.empty()) handleTextureSet( - texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset, boundTextures); + texprop->mTextureSet.getPtr(), wrapS, wrapT, node->getName(), stateset, boundTextures); handleTextureControllers(texprop, composite, stateset, animflags); if (texprop->doubleSided()) stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); @@ -2541,33 +2510,20 @@ namespace NifOsg node->setUserValue("shaderPrefix", std::string("bs/nolighting")); node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); - Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName); - if (material) + clearBoundTextures(stateset, boundTextures); + if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName)) { - handleShaderMaterial(material, stateset, boundTextures); + handleShaderMaterialNodeProperties(material, stateset, boundTextures); break; } if (!texprop->mSourceTexture.empty()) { - if (!boundTextures.empty()) - { - for (unsigned int i = 0; i < boundTextures.size(); ++i) - stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF); - boundTextures.clear(); - } - std::string filename = Misc::ResourceHelpers::correctTexturePath( - texprop->mSourceTexture, mImageManager->getVFS()); - osg::ref_ptr image = mImageManager->getImage(filename); - osg::ref_ptr texture2d = new osg::Texture2D(image); - texture2d->setName("diffuseMap"); - if (image) - texture2d->setTextureSize(image->s(), image->t()); - handleTextureWrapping(texture2d, (texprop->mClamp >> 1) & 0x1, texprop->mClamp & 0x1); - const unsigned int texUnit = 0; const unsigned int uvSet = 0; - stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); - boundTextures.push_back(uvSet); - + const bool wrapS = (texprop->mClamp >> 1) & 0x1; + const bool wrapT = texprop->mClamp & 0x1; + unsigned int texUnit = boundTextures.size(); + attachExternalTexture( + "diffuseMap", texprop->mSourceTexture, wrapS, wrapT, uvSet, stateset, boundTextures); { osg::ref_ptr texMat(new osg::TexMat); // This handles 20.2.0.7 UV settings like 4.0.0.2 UV settings (see NifOsg::UVController) @@ -2819,40 +2775,11 @@ namespace NifOsg case Nif::RC_BSLightingShaderProperty: { auto shaderprop = static_cast(property); - Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName); - if (shaderMat) + if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName)) { - mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency); - if (shaderMat->mAlphaTest) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - osg::ref_ptr alphaFunc(new osg::AlphaFunc( - osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold / 255.f)); - alphaFunc = shareAttribute(alphaFunc); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - } - if (shaderMat->mAlphaBlend) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - osg::ref_ptr blendFunc( - new osg::BlendFunc(getBlendMode(shaderMat->mSourceBlendMode), - getBlendMode(shaderMat->mDestinationBlendMode))); - blendFunc = shareAttribute(blendFunc); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); - hasSortAlpha = true; - if (!mPushedSorter) - setBin_Transparent(stateset); - } - if (shaderMat->mDecal) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - if (!mPushedSorter && !hasSortAlpha) - stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); - osg::ref_ptr polygonOffset(new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); - polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); - stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); - } + handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); + if (shaderMat->mAlphaBlend && !mPushedSorter) + setBin_Transparent(node->getOrCreateStateSet()); if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) { auto bgsm = static_cast(shaderMat.get()); @@ -2860,8 +2787,6 @@ namespace NifOsg = false; // bgsm->mSpecularEnabled; disabled until it can be implemented properly specStrength = bgsm->mSpecularMult; emissiveMult = bgsm->mEmittanceMult; - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mEmittanceColor, 1.f)); - mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mSpecularColor, 1.f)); } break; } @@ -2888,47 +2813,11 @@ namespace NifOsg case Nif::RC_BSEffectShaderProperty: { auto shaderprop = static_cast(property); - Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName); - if (shaderMat) + if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName)) { - mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency); - if (shaderMat->mAlphaTest) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - osg::ref_ptr alphaFunc(new osg::AlphaFunc( - osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold / 255.f)); - alphaFunc = shareAttribute(alphaFunc); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - } - if (shaderMat->mAlphaBlend) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - osg::ref_ptr blendFunc( - new osg::BlendFunc(getBlendMode(shaderMat->mSourceBlendMode), - getBlendMode(shaderMat->mDestinationBlendMode))); - blendFunc = shareAttribute(blendFunc); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); - hasSortAlpha = true; - if (!mPushedSorter) - setBin_Transparent(stateset); - } - if (shaderMat->mDecal) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - if (!mPushedSorter && !hasSortAlpha) - stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); - osg::ref_ptr polygonOffset(new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); - polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); - stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); - } - if (shaderMat->mShaderType == Bgsm::ShaderType::Effect) - { - auto bgem = static_cast(shaderMat.get()); - mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgem->mEmittanceColor, 1.f)); - if (bgem->mSoft) - SceneUtil::setupSoftEffect(*node, bgem->mSoftDepth, true, bgem->mSoftDepth); - } + handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); + if (shaderMat->mAlphaBlend && !mPushedSorter) + setBin_Transparent(node->getOrCreateStateSet()); break; } if (shaderprop->decal()) From 5789eb73b104bc93b433528bd0df476b7b843682 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 19 Apr 2024 16:47:00 +0300 Subject: [PATCH 432/451] Deduplicate decal and alpha handling in NifLoader --- components/nifosg/nifloader.cpp | 160 ++++++++++++++------------------ 1 file changed, 69 insertions(+), 91 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 7177377771..5af4e57c53 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2209,36 +2209,79 @@ namespace NifOsg handleDepthFlags(stateset, material->mDepthTest, material->mDepthWrite); } - void handleShaderMaterialDrawableProperties( - Bgsm::MaterialFilePtr shaderMat, osg::ref_ptr mat, osg::Node& node, bool& hasSortAlpha) + void handleDecal(bool hasSortAlpha, osg::ref_ptr stateset) { - mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency); - if (shaderMat->mAlphaTest) + osg::ref_ptr polygonOffset(new osg::PolygonOffset); + polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); + polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); + polygonOffset = shareAttribute(polygonOffset); + stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + if (!mPushedSorter && !hasSortAlpha) + stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); + } + + void handleAlphaTesting( + bool enabled, osg::AlphaFunc::ComparisonFunction function, int threshold, osg::Node& node) + { + if (enabled) { - osg::StateSet* stateset = node.getOrCreateStateSet(); - osg::ref_ptr alphaFunc( - new osg::AlphaFunc(osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold / 255.f)); + osg::ref_ptr alphaFunc(new osg::AlphaFunc(function, threshold / 255.f)); alphaFunc = shareAttribute(alphaFunc); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); } - if (shaderMat->mAlphaBlend) + else if (osg::StateSet* stateset = node.getStateSet()) { - osg::StateSet* stateset = node.getOrCreateStateSet(); - osg::ref_ptr blendFunc(new osg::BlendFunc( - getBlendMode(shaderMat->mSourceBlendMode), getBlendMode(shaderMat->mDestinationBlendMode))); + stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); + stateset->removeMode(GL_ALPHA_TEST); + } + } + + void handleAlphaBlending( + bool enabled, int sourceMode, int destMode, bool sort, bool& hasSortAlpha, osg::Node& node) + { + if (enabled) + { + osg::ref_ptr stateset = node.getOrCreateStateSet(); + osg::ref_ptr blendFunc( + new osg::BlendFunc(getBlendMode(sourceMode), getBlendMode(destMode))); + // on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL. + // This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug. + // Either way, D3D8.1 doesn't do that, so adapt the destination factor. + if (blendFunc->getDestination() == GL_DST_ALPHA) + blendFunc->setDestination(GL_ONE); blendFunc = shareAttribute(blendFunc); stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); - hasSortAlpha = true; + + if (sort) + { + hasSortAlpha = true; + if (!mPushedSorter) + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + } + else if (!mPushedSorter) + { + stateset->setRenderBinToInherit(); + } } + else if (osg::ref_ptr stateset = node.getStateSet()) + { + stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); + stateset->removeMode(GL_BLEND); + if (!mPushedSorter) + stateset->setRenderBinToInherit(); + } + } + + void handleShaderMaterialDrawableProperties( + Bgsm::MaterialFilePtr shaderMat, osg::ref_ptr mat, osg::Node& node, bool& hasSortAlpha) + { + mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency); + handleAlphaTesting(shaderMat->mAlphaTest, osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold, node); + handleAlphaBlending(shaderMat->mAlphaBlend, shaderMat->mSourceBlendMode, shaderMat->mDestinationBlendMode, + true, hasSortAlpha, node); if (shaderMat->mDecal) { - osg::StateSet* stateset = node.getOrCreateStateSet(); - if (!mPushedSorter && !hasSortAlpha) - stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); - osg::ref_ptr polygonOffset(new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); - polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); - stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); + handleDecal(hasSortAlpha, node.getOrCreateStateSet()); } if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) { @@ -2627,12 +2670,9 @@ namespace NifOsg bool hasMatCtrl = false; bool hasSortAlpha = false; - osg::StateSet* blendFuncStateSet = nullptr; - auto setBin_Transparent = [](osg::StateSet* ss) { ss->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); }; auto setBin_BackToFront = [](osg::StateSet* ss) { ss->setRenderBinDetails(0, "SORT_BACK_TO_FRONT"); }; auto setBin_Traversal = [](osg::StateSet* ss) { ss->setRenderBinDetails(2, "TraversalOrderBin"); }; - auto setBin_Inherit = [](osg::StateSet* ss) { ss->setRenderBinToInherit(); }; auto lightmode = Nif::NiVertexColorProperty::LightMode::LightMode_EmiAmbDif; float emissiveMult = 1.f; @@ -2718,52 +2758,10 @@ namespace NifOsg case Nif::RC_NiAlphaProperty: { const Nif::NiAlphaProperty* alphaprop = static_cast(property); - if (alphaprop->useAlphaBlending()) - { - osg::ref_ptr blendFunc( - new osg::BlendFunc(getBlendMode(alphaprop->sourceBlendMode()), - getBlendMode(alphaprop->destinationBlendMode()))); - // on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL. - // This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug. - // Either way, D3D8.1 doesn't do that, so adapt the destination factor. - if (blendFunc->getDestination() == GL_DST_ALPHA) - blendFunc->setDestination(GL_ONE); - blendFunc = shareAttribute(blendFunc); - node->getOrCreateStateSet()->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); - - if (!alphaprop->noSorter()) - { - hasSortAlpha = true; - if (!mPushedSorter) - setBin_Transparent(node->getStateSet()); - } - else - { - if (!mPushedSorter) - setBin_Inherit(node->getStateSet()); - } - } - else if (osg::StateSet* stateset = node->getStateSet()) - { - stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); - stateset->removeMode(GL_BLEND); - blendFuncStateSet = stateset; - if (!mPushedSorter) - blendFuncStateSet->setRenderBinToInherit(); - } - - if (alphaprop->useAlphaTesting()) - { - osg::ref_ptr alphaFunc(new osg::AlphaFunc( - getTestMode(alphaprop->alphaTestMode()), alphaprop->mThreshold / 255.f)); - alphaFunc = shareAttribute(alphaFunc); - node->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - } - else if (osg::StateSet* stateset = node->getStateSet()) - { - stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); - stateset->removeMode(GL_ALPHA_TEST); - } + handleAlphaBlending(alphaprop->useAlphaBlending(), alphaprop->sourceBlendMode(), + alphaprop->destinationBlendMode(), !alphaprop->noSorter(), hasSortAlpha, *node); + handleAlphaTesting(alphaprop->useAlphaTesting(), getTestMode(alphaprop->alphaTestMode()), + alphaprop->mThreshold, *node); break; } case Nif::RC_BSShaderPPLightingProperty: @@ -2778,8 +2776,6 @@ namespace NifOsg if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName)) { handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); - if (shaderMat->mAlphaBlend && !mPushedSorter) - setBin_Transparent(node->getOrCreateStateSet()); if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) { auto bgsm = static_cast(shaderMat.get()); @@ -2799,15 +2795,7 @@ namespace NifOsg specStrength = shaderprop->mSpecStrength; specEnabled = shaderprop->specular(); if (shaderprop->decal()) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - if (!mPushedSorter && !hasSortAlpha) - stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); - osg::ref_ptr polygonOffset(new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); - polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); - stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); - } + handleDecal(hasSortAlpha, node->getOrCreateStateSet()); break; } case Nif::RC_BSEffectShaderProperty: @@ -2816,20 +2804,10 @@ namespace NifOsg if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName)) { handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); - if (shaderMat->mAlphaBlend && !mPushedSorter) - setBin_Transparent(node->getOrCreateStateSet()); break; } if (shaderprop->decal()) - { - osg::StateSet* stateset = node->getOrCreateStateSet(); - if (!mPushedSorter && !hasSortAlpha) - stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT"); - osg::ref_ptr polygonOffset(new osg::PolygonOffset); - polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); - polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); - stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); - } + handleDecal(hasSortAlpha, node->getOrCreateStateSet()); if (shaderprop->softEffect()) SceneUtil::setupSoftEffect( *node, shaderprop->mFalloffDepth, true, shaderprop->mFalloffDepth); From 4e3d45db1baf2f656ac1f26cee1d8d31e7c74678 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 07:46:41 +0300 Subject: [PATCH 433/451] Deduplicate file handling in niftest --- apps/niftest/niftest.cpp | 79 ++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 52 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index c364303376..8f8c408a87 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -26,7 +26,7 @@ namespace bpo = boost::program_options; /// See if the file has the named extension -bool hasExtension(const std::filesystem::path& filename, const std::string& extensionToFind) +bool hasExtension(const std::filesystem::path& filename, std::string_view extensionToFind) { const auto extension = Files::pathToUnicodeString(filename.extension()); return Misc::StringUtils::ciEqual(extension, extensionToFind); @@ -59,16 +59,17 @@ std::unique_ptr makeArchive(const std::filesystem::path& path) return nullptr; } -void readNIF( +void readFile( const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) { const std::string pathStr = Files::pathToUnicodeString(path); + const bool isNif = isNIF(path); if (!quiet) { - if (hasExtension(path, ".kf")) - std::cout << "Reading KF file '" << pathStr << "'"; + if (isNif) + std::cout << "Reading " << (hasExtension(path, ".nif") ? "NIF" : "KF") << " file '" << pathStr << "'"; else - std::cout << "Reading NIF file '" << pathStr << "'"; + std::cout << "Reading " << (hasExtension(path, ".bgsm") ? "BGSM" : "BGEM") << " file '" << pathStr << "'"; if (!source.empty()) std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; std::cout << std::endl; @@ -76,41 +77,23 @@ void readNIF( const std::filesystem::path fullPath = !source.empty() ? source / path : path; try { - Nif::NIFFile file(Files::pathToUnicodeString(fullPath)); - Nif::Reader reader(file, nullptr); - if (vfs != nullptr) - reader.parse(vfs->get(pathStr)); - else - reader.parse(Files::openConstrainedFileStream(fullPath)); - } - catch (std::exception& e) - { - std::cerr << "Failed to read '" << pathStr << "':" << std::endl << e.what() << std::endl; - } -} - -void readMaterial( - const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) -{ - const std::string pathStr = Files::pathToUnicodeString(path); - if (!quiet) - { - if (hasExtension(path, ".bgem")) - std::cout << "Reading BGEM file '" << pathStr << "'"; - else - std::cout << "Reading BGSM file '" << pathStr << "'"; - if (!source.empty()) - std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; - std::cout << std::endl; - } - const std::filesystem::path fullPath = !source.empty() ? source / path : path; - try - { - Bgsm::Reader reader; - if (vfs != nullptr) - reader.parse(vfs->get(pathStr)); + if (isNif) + { + Nif::NIFFile file(Files::pathToUnicodeString(fullPath)); + Nif::Reader reader(file, nullptr); + if (vfs != nullptr) + reader.parse(vfs->get(pathStr)); + else + reader.parse(Files::openConstrainedFileStream(fullPath)); + } else - reader.parse(Files::openConstrainedFileStream(fullPath)); + { + Bgsm::Reader reader; + if (vfs != nullptr) + reader.parse(vfs->get(pathStr)); + else + reader.parse(Files::openConstrainedFileStream(fullPath)); + } } catch (std::exception& e) { @@ -134,13 +117,9 @@ void readVFS(std::unique_ptr&& archive, const std::filesystem::pat for (const auto& name : vfs.getRecursiveDirectoryIterator("")) { - if (isNIF(name.value())) + if (isNIF(name.value()) || isMaterial(name.value())) { - readNIF(archivePath, name.value(), &vfs, quiet); - } - else if (isMaterial(name.value())) - { - readMaterial(archivePath, name.value(), &vfs, quiet); + readFile(archivePath, name.value(), &vfs, quiet); } } @@ -262,13 +241,9 @@ int main(int argc, char** argv) const std::string pathStr = Files::pathToUnicodeString(path); try { - if (isNIF(path)) - { - readNIF({}, path, vfs.get(), quiet); - } - else if (isMaterial(path)) + if (isNIF(path) || isMaterial(path)) { - readMaterial({}, path, vfs.get(), quiet); + readFile({}, path, vfs.get(), quiet); } else if (auto archive = makeArchive(path)) { @@ -276,7 +251,7 @@ int main(int argc, char** argv) } else { - std::cerr << "Error: '" << pathStr << "' is not a NIF/KF file, BSA/BA2 archive, or directory" + std::cerr << "Error: '" << pathStr << "' is not a NIF/KF/BGEM/BGSM file, BSA/BA2 archive, or directory" << std::endl; } } From 6f9206428d882a176952c6fa8bc3014a9c614bdc Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 07:53:16 +0300 Subject: [PATCH 434/451] Don't ignore material files in NifLoader tests --- apps/openmw_test_suite/nifosg/testnifloader.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index cdab51e6c2..fa023fff0d 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ namespace { VFS::Manager mVfs; Resource::ImageManager mImageManager{ &mVfs, 0 }; + Resource::BgsmFileManager mMaterialManager{ &mVfs, 0 }; const osgDB::ReaderWriter* mReaderWriter = osgDB::Registry::instance()->getReaderWriterForExtension("osgt"); osg::ref_ptr mOptions = new osgDB::Options; @@ -70,7 +72,7 @@ namespace init(node); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager, nullptr); + auto result = Loader::load(file, &mImageManager, &mMaterialManager); EXPECT_EQ(serialize(*result), R"( osg::Group { UniqueID 1 @@ -259,7 +261,7 @@ osg::Group { node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager, nullptr); + auto result = Loader::load(file, &mImageManager, &mMaterialManager); EXPECT_EQ(serialize(*result), formatOsgNodeForBSShaderProperty(GetParam().mExpectedShaderPrefix)); } @@ -289,7 +291,7 @@ osg::Group { node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); - auto result = Loader::load(file, &mImageManager, nullptr); + auto result = Loader::load(file, &mImageManager, &mMaterialManager); EXPECT_EQ(serialize(*result), formatOsgNodeForBSLightingShaderProperty(GetParam().mExpectedShaderPrefix)); } From 32d24e73fedd482f531d285b3daa211ebcce7131 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 08:00:27 +0300 Subject: [PATCH 435/451] Update changelog (#7777, Bethesda material files) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35cf145f82..caa3299c97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -219,6 +219,7 @@ Feature #7652: Sort inactive post processing shaders list properly Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7709: Improve resolution selection in Launcher + Feature #7777: Support external Bethesda material files (BGSM/BGEM) Feature #7792: Support Timescale Clouds Feature #7795: Support MaxNumberRipples INI setting Feature #7805: Lua Menu context From 8325e100df61854a29494daf815b9be14a65820e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 08:12:53 +0300 Subject: [PATCH 436/451] More decal deduplication --- components/nifosg/nifloader.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 5af4e57c53..4616ac440f 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2209,8 +2209,11 @@ namespace NifOsg handleDepthFlags(stateset, material->mDepthTest, material->mDepthWrite); } - void handleDecal(bool hasSortAlpha, osg::ref_ptr stateset) + void handleDecal(bool enabled, bool hasSortAlpha, osg::Node& node) { + if (!enabled) + return; + osg::ref_ptr stateset = node.getOrCreateStateSet(); osg::ref_ptr polygonOffset(new osg::PolygonOffset); polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f); polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f); @@ -2279,10 +2282,7 @@ namespace NifOsg handleAlphaTesting(shaderMat->mAlphaTest, osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold, node); handleAlphaBlending(shaderMat->mAlphaBlend, shaderMat->mSourceBlendMode, shaderMat->mDestinationBlendMode, true, hasSortAlpha, node); - if (shaderMat->mDecal) - { - handleDecal(hasSortAlpha, node.getOrCreateStateSet()); - } + handleDecal(shaderMat->mDecal, hasSortAlpha, node); if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) { auto bgsm = static_cast(shaderMat.get()); @@ -2794,8 +2794,7 @@ namespace NifOsg emissiveMult = shaderprop->mEmissiveMult; specStrength = shaderprop->mSpecStrength; specEnabled = shaderprop->specular(); - if (shaderprop->decal()) - handleDecal(hasSortAlpha, node->getOrCreateStateSet()); + handleDecal(shaderprop->decal(), hasSortAlpha, *node); break; } case Nif::RC_BSEffectShaderProperty: @@ -2806,8 +2805,7 @@ namespace NifOsg handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); break; } - if (shaderprop->decal()) - handleDecal(hasSortAlpha, node->getOrCreateStateSet()); + handleDecal(shaderprop->decal(), hasSortAlpha, *node); if (shaderprop->softEffect()) SceneUtil::setupSoftEffect( *node, shaderprop->mFalloffDepth, true, shaderprop->mFalloffDepth); From ea5e101821debafb63f47d263c507ce0b4bc4248 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 08:24:50 +0300 Subject: [PATCH 437/451] Handle glow maps for BGSM files --- components/nifosg/nifloader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 4616ac440f..f661e1e8c6 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2188,6 +2188,9 @@ namespace NifOsg if (!bgsm->mNormalMap.empty()) attachExternalTexture("normalMap", bgsm->mNormalMap, wrapS, wrapT, uvSet, stateset, boundTextures); + if (bgsm->mGlowMapEnabled && !bgsm->mGlowMap.empty()) + attachExternalTexture("emissiveMap", bgsm->mGlowMap, wrapS, wrapT, uvSet, stateset, boundTextures); + if (bgsm->mTree) stateset->addUniform(new osg::Uniform("useTreeAnim", true)); } From 6be2bb70c3cbf478b21d40405e031bfada9e57e4 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 18:11:56 +0300 Subject: [PATCH 438/451] Remove unused remnants of NIFStream from BGSMStream --- components/bgsm/reader.cpp | 2 +- components/bgsm/reader.hpp | 2 - components/bgsm/stream.cpp | 96 +++++--------------------------------- components/bgsm/stream.hpp | 73 ++--------------------------- 4 files changed, 17 insertions(+), 156 deletions(-) diff --git a/components/bgsm/reader.cpp b/components/bgsm/reader.cpp index c89d872bd7..47c052fb81 100644 --- a/components/bgsm/reader.cpp +++ b/components/bgsm/reader.cpp @@ -11,7 +11,7 @@ namespace Bgsm { void Reader::parse(Files::IStreamPtr&& inputStream) { - BGSMStream stream(*this, std::move(inputStream)); + BGSMStream stream(std::move(inputStream)); std::array signature; stream.readArray(signature); diff --git a/components/bgsm/reader.hpp b/components/bgsm/reader.hpp index 2d669900ad..2d8a0ed481 100644 --- a/components/bgsm/reader.hpp +++ b/components/bgsm/reader.hpp @@ -19,8 +19,6 @@ namespace Bgsm public: void parse(Files::IStreamPtr&& stream); - std::uint32_t getVersion() const { return mFile->mVersion; } - std::unique_ptr& getFile() { return mFile; } }; } diff --git a/components/bgsm/stream.cpp b/components/bgsm/stream.cpp index 00cc382d3f..2c11066709 100644 --- a/components/bgsm/stream.cpp +++ b/components/bgsm/stream.cpp @@ -1,66 +1,8 @@ #include "stream.hpp" -#include - -#include "reader.hpp" - -namespace -{ - - // Read a range of elements into a dynamic buffer per-element - // This one should be used if the type cannot be read contiguously - // (e.g. quaternions) - template - void readRange(Bgsm::BGSMStream& stream, T* dest, size_t size) - { - for (T& value : std::span(dest, size)) - stream.read(value); - } - - // Read a range of elements into a dynamic buffer - // This one should be used if the type can be read contiguously as an array of a different type - // (e.g. osg::VecXf can be read as a float array of X elements) - template - void readAlignedRange(Files::IStreamPtr& stream, T* dest, size_t size) - { - static_assert(std::is_standard_layout_v); - static_assert(std::alignment_of_v == std::alignment_of_v); - static_assert(sizeof(T) == sizeof(elementType) * numElements); - Bgsm::readDynamicBufferOfType(stream, reinterpret_cast(dest), size * numElements); - } - -} - namespace Bgsm { - std::uint32_t BGSMStream::getVersion() const - { - return mReader.getVersion(); - } - - std::string BGSMStream::getSizedString(size_t length) - { - // Prevent potential memory allocation freezes; strings this long are not expected in BGSM - if (length > 1024) - throw std::runtime_error("Requested string length is too large: " + std::to_string(length)); - std::string str(length, '\0'); - mStream->read(str.data(), length); - if (mStream->bad()) - throw std::runtime_error("Failed to read sized string of " + std::to_string(length) + " chars"); - size_t end = str.find('\0'); - if (end != std::string::npos) - str.erase(end); - return str; - } - - void BGSMStream::getSizedStrings(std::vector& vec, size_t size) - { - vec.resize(size); - for (size_t i = 0; i < vec.size(); i++) - vec[i] = getSizedString(); - } - template <> void BGSMStream::read(osg::Vec2f& vec) { @@ -82,32 +24,18 @@ namespace Bgsm template <> void BGSMStream::read(std::string& str) { - str = getSizedString(); - } - - template <> - void BGSMStream::read(osg::Vec2f* dest, size_t size) - { - readAlignedRange(mStream, dest, size); - } - - template <> - void BGSMStream::read(osg::Vec3f* dest, size_t size) - { - readAlignedRange(mStream, dest, size); - } - - template <> - void BGSMStream::read(osg::Vec4f* dest, size_t size) - { - readAlignedRange(mStream, dest, size); - } - - template <> - void BGSMStream::read(std::string* dest, size_t size) - { - for (std::string& value : std::span(dest, size)) - value = getSizedString(); + std::uint32_t length; + read(length); + // Prevent potential memory allocation freezes; strings this long are not expected in BGSM + if (length > 1024) + throw std::runtime_error("Requested string length is too large: " + std::to_string(length)); + str = std::string(length, '\0'); + mStream->read(str.data(), length); + if (mStream->bad()) + throw std::runtime_error("Failed to read sized string of " + std::to_string(length) + " chars"); + std::size_t end = str.find('\0'); + if (end != std::string::npos) + str.erase(end); } } diff --git a/components/bgsm/stream.hpp b/components/bgsm/stream.hpp index 2e03a52dd4..a355523367 100644 --- a/components/bgsm/stream.hpp +++ b/components/bgsm/stream.hpp @@ -8,23 +8,21 @@ #include #include #include -#include #include #include +#include #include #include namespace Bgsm { - class Reader; - template inline void readBufferOfType(Files::IStreamPtr& pIStream, T* dest) { static_assert(std::is_arithmetic_v, "Buffer element type is not arithmetic"); - pIStream->read((char*)dest, numInstances * sizeof(T)); + pIStream->read(reinterpret_cast(dest), numInstances * sizeof(T)); if (pIStream->bad()) throw std::runtime_error("Failed to read typed (" + std::string(typeid(T).name()) + ") buffer of " + std::to_string(numInstances) + " instances"); @@ -39,35 +37,16 @@ namespace Bgsm readBufferOfType(pIStream, static_cast(dest)); } - template - inline void readDynamicBufferOfType(Files::IStreamPtr& pIStream, T* dest, std::size_t numInstances) - { - static_assert(std::is_arithmetic_v, "Buffer element type is not arithmetic"); - pIStream->read((char*)dest, numInstances * sizeof(T)); - if (pIStream->bad()) - throw std::runtime_error("Failed to read typed (" + std::string(typeid(T).name()) + ") dynamic buffer of " - + std::to_string(numInstances) + " instances"); - if constexpr (Misc::IS_BIG_ENDIAN) - for (std::size_t i = 0; i < numInstances; i++) - Misc::swapEndiannessInplace(dest[i]); - } - class BGSMStream { - const Reader& mReader; Files::IStreamPtr mStream; public: - explicit BGSMStream(const Reader& reader, Files::IStreamPtr&& stream) - : mReader(reader) - , mStream(std::move(stream)) + explicit BGSMStream(Files::IStreamPtr&& stream) + : mStream(std::move(stream)) { } - const Reader& getFile() const { return mReader; } - - std::uint32_t getVersion() const; - void skip(size_t size) { mStream->ignore(size); } /// Read into a single instance of type @@ -83,41 +62,6 @@ namespace Bgsm { readBufferOfType(mStream, arr.data()); } - - /// Read instances of type into a dynamic buffer - template - void read(T* dest, size_t size) - { - readDynamicBufferOfType(mStream, dest, size); - } - - /// Read multiple instances of type into a vector - template - void readVector(std::vector& vec, size_t size) - { - if (size == 0) - return; - vec.resize(size); - read(vec.data(), size); - } - - /// Extract an instance of type - template - T get() - { - T data; - read(data); - return data; - } - - /// Read a string of the given length - std::string getSizedString(size_t length); - - /// Read a string of the length specified in the file - std::string getSizedString() { return getSizedString(get()); } - - /// Read a list of strings - void getSizedStrings(std::vector& vec, size_t size); }; template <> @@ -128,15 +72,6 @@ namespace Bgsm void BGSMStream::read(osg::Vec4f& vec); template <> void BGSMStream::read(std::string& str); - - template <> - void BGSMStream::read(osg::Vec2f* dest, size_t size); - template <> - void BGSMStream::read(osg::Vec3f* dest, size_t size); - template <> - void BGSMStream::read(osg::Vec4f* dest, size_t size); - template <> - void BGSMStream::read(std::string* dest, size_t size); } #endif From 77c3cd44674eb9e875e3a87f6f672bdf1e16bf7d Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 18:48:12 +0300 Subject: [PATCH 439/451] More BGSM cleanup --- components/bgsm/file.cpp | 40 ++++++++----------------- components/bgsm/file.hpp | 2 +- components/bgsm/reader.cpp | 1 - components/bgsm/stream.cpp | 2 -- components/nifosg/nifloader.cpp | 22 +++++++------- components/resource/bgsmfilemanager.cpp | 4 --- components/resource/bgsmfilemanager.hpp | 2 +- 7 files changed, 25 insertions(+), 48 deletions(-) diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp index f330d8a84e..6b763321be 100644 --- a/components/bgsm/file.cpp +++ b/components/bgsm/file.cpp @@ -38,9 +38,7 @@ namespace Bgsm } stream.read(mGrayscaleToPaletteColor); if (mVersion >= 6) - { stream.read(mMaskWrites); - } } void BGSMFile::read(BGSMStream& stream) @@ -59,9 +57,7 @@ namespace Bgsm stream.read(mLightingMap); stream.read(mFlowMap); if (mVersion >= 17) - { stream.read(mDistanceFieldAlphaMap); - } } else { @@ -98,9 +94,7 @@ namespace Bgsm stream.read(mWetnessControlSpecPowerScale); stream.read(mWetnessControlSpecMinvar); if (mVersion < 10) - { stream.read(mWetnessControlEnvMapScale); - } stream.read(mWetnessControlFresnelPower); stream.read(mWetnessControlMetalness); if (mVersion >= 3) @@ -116,9 +110,7 @@ namespace Bgsm stream.read(mAnisoLighting); stream.read(mEmitEnabled); if (mEmitEnabled) - { stream.read(mEmittanceColor); - } stream.read(mEmittanceMult); stream.read(mModelSpaceNormals); stream.read(mExternalEmittance); @@ -181,14 +173,14 @@ namespace Bgsm stream.read(mEnvMap); stream.read(mNormalMap); stream.read(mEnvMapMask); + if (mVersion >= 11) + { + stream.read(mSpecularMap); + stream.read(mLightingMap); + stream.read(mGlowMap); + } if (mVersion >= 10) { - if (mVersion >= 11) - { - stream.read(mSpecularMap); - stream.read(mLightingMap); - stream.read(mGlowMap); - } stream.read(mEnvMapEnabled); stream.read(mEnvMapMaskScale); } @@ -205,20 +197,12 @@ namespace Bgsm stream.read(mEnvmapMinLOD); stream.read(mSoftDepth); if (mVersion >= 11) - { stream.read(mEmittanceColor); - if (mVersion >= 15) - { - stream.read(mAdaptiveEmissiveExposureParams); - if (mVersion >= 16) - { - stream.read(mGlowMapEnabled); - if (mVersion >= 20) - { - stream.read(mEffectPbrSpecular); - } - } - } - } + if (mVersion >= 15) + stream.read(mAdaptiveEmissiveExposureParams); + if (mVersion >= 16) + stream.read(mGlowMapEnabled); + if (mVersion >= 20) + stream.read(mEffectPbrSpecular); } } diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp index 3524297e2d..d3fb189256 100644 --- a/components/bgsm/file.hpp +++ b/components/bgsm/file.hpp @@ -50,7 +50,7 @@ namespace Bgsm MaterialFile() = default; virtual void read(BGSMStream& stream); - virtual ~MaterialFile() {} + virtual ~MaterialFile() = default; }; struct BGSMFile : MaterialFile diff --git a/components/bgsm/reader.cpp b/components/bgsm/reader.cpp index 47c052fb81..facdee9fb2 100644 --- a/components/bgsm/reader.cpp +++ b/components/bgsm/reader.cpp @@ -31,5 +31,4 @@ namespace Bgsm mFile->read(stream); } - } diff --git a/components/bgsm/stream.cpp b/components/bgsm/stream.cpp index 2c11066709..c4fa9c1d8c 100644 --- a/components/bgsm/stream.cpp +++ b/components/bgsm/stream.cpp @@ -2,7 +2,6 @@ namespace Bgsm { - template <> void BGSMStream::read(osg::Vec2f& vec) { @@ -37,5 +36,4 @@ namespace Bgsm if (end != std::string::npos) str.erase(end); } - } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index f661e1e8c6..05a8378c11 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2151,18 +2151,19 @@ namespace NifOsg handleTextureControllers(texprop, composite, stateset, animflags); } - Bgsm::MaterialFilePtr getShaderMaterial(std::string_view path) const + static Bgsm::MaterialFilePtr getShaderMaterial( + std::string_view path, Resource::BgsmFileManager* materialManager) { - if (!mMaterialManager) + if (!materialManager) return nullptr; if (!Misc::StringUtils::ciEndsWith(path, ".bgem") && !Misc::StringUtils::ciEndsWith(path, ".bgsm")) return nullptr; - std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, mMaterialManager->getVFS()); + std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, materialManager->getVFS()); try { - return mMaterialManager->get(VFS::Path::Normalized(normalizedPath)); + return materialManager->get(VFS::Path::Normalized(normalizedPath)); } catch (std::exception& e) { @@ -2528,7 +2529,7 @@ namespace NifOsg node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); clearBoundTextures(stateset, boundTextures); - if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName)) + if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager)) { handleShaderMaterialNodeProperties(material, stateset, boundTextures); break; @@ -2557,7 +2558,7 @@ namespace NifOsg node->setUserValue("shaderRequired", shaderRequired); osg::StateSet* stateset = node->getOrCreateStateSet(); clearBoundTextures(stateset, boundTextures); - if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName)) + if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager)) { handleShaderMaterialNodeProperties(material, stateset, boundTextures); break; @@ -2776,15 +2777,14 @@ namespace NifOsg case Nif::RC_BSLightingShaderProperty: { auto shaderprop = static_cast(property); - if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName)) + if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName, mMaterialManager)) { handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) { auto bgsm = static_cast(shaderMat.get()); - specEnabled - = false; // bgsm->mSpecularEnabled; disabled until it can be implemented properly - specStrength = bgsm->mSpecularMult; + specEnabled = false; // bgsm->mSpecularEnabled; TODO: PBR specular lighting + specStrength = 1.f; // bgsm->mSpecularMult; emissiveMult = bgsm->mEmittanceMult; } break; @@ -2803,7 +2803,7 @@ namespace NifOsg case Nif::RC_BSEffectShaderProperty: { auto shaderprop = static_cast(property); - if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName)) + if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName, mMaterialManager)) { handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); break; diff --git a/components/resource/bgsmfilemanager.cpp b/components/resource/bgsmfilemanager.cpp index 5155db17ce..7f749e9453 100644 --- a/components/resource/bgsmfilemanager.cpp +++ b/components/resource/bgsmfilemanager.cpp @@ -1,7 +1,5 @@ #include "bgsmfilemanager.hpp" -#include - #include #include @@ -36,8 +34,6 @@ namespace Resource { } - BgsmFileManager::~BgsmFileManager() = default; - Bgsm::MaterialFilePtr BgsmFileManager::get(VFS::Path::NormalizedView name) { osg::ref_ptr obj = mCache->getRefFromObjectCache(name); diff --git a/components/resource/bgsmfilemanager.hpp b/components/resource/bgsmfilemanager.hpp index b7c0d07c5a..3c77c2c665 100644 --- a/components/resource/bgsmfilemanager.hpp +++ b/components/resource/bgsmfilemanager.hpp @@ -14,7 +14,7 @@ namespace Resource { public: BgsmFileManager(const VFS::Manager* vfs, double expiryDelay); - ~BgsmFileManager(); + ~BgsmFileManager() = default; /// Retrieve a material file from the cache or load it from the VFS if not cached yet. Bgsm::MaterialFilePtr get(VFS::Path::NormalizedView name); From a863899eb1a158fce32cc3193e8899f17c4d4c24 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 14 Apr 2024 19:51:48 +0200 Subject: [PATCH 440/451] Use normalized path for SoundManager::streamMusic --- apps/openmw/engine.cpp | 6 ++--- apps/openmw/mwbase/soundmanager.hpp | 2 +- apps/openmw/mwgui/levelupdialog.cpp | 5 ++-- apps/openmw/mwlua/soundbindings.cpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 4 ++- apps/openmw/mwscript/soundextensions.cpp | 2 +- apps/openmw/mwsound/constants.hpp | 3 +++ apps/openmw/mwsound/soundmanagerimp.cpp | 31 +++++++++++------------- apps/openmw/mwsound/soundmanagerimp.hpp | 10 ++++---- components/misc/resourcehelpers.cpp | 7 +++--- components/misc/resourcehelpers.hpp | 6 ++--- 11 files changed, 40 insertions(+), 38 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 63473fe67d..179dbcdc32 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -64,6 +64,7 @@ #include "mwscript/interpretercontext.hpp" #include "mwscript/scriptmanagerimp.hpp" +#include "mwsound/constants.hpp" #include "mwsound/soundmanagerimp.hpp" #include "mwworld/class.hpp" @@ -987,9 +988,8 @@ void OMW::Engine::go() // start in main menu mWindowManager->pushGuiMode(MWGui::GM_MainMenu); - std::string titlefile = "music/special/morrowind title.mp3"; - if (mVFS->exists(titlefile)) - mSoundManager->streamMusic(titlefile, MWSound::MusicType::Special); + if (mVFS->exists(MWSound::titleMusic)) + mSoundManager->streamMusic(MWSound::titleMusic, MWSound::MusicType::Special); else Log(Debug::Warning) << "Title music not found"; diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index a55d696224..ab3f9c5605 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -117,7 +117,7 @@ namespace MWBase virtual void stopMusic() = 0; ///< Stops music if it's playing - virtual void streamMusic(const std::string& filename, MWSound::MusicType type, float fade = 1.f) = 0; + virtual void streamMusic(VFS::Path::NormalizedView filename, MWSound::MusicType type, float fade = 1.f) = 0; ///< Play a soundifle /// \param filename name of a sound file in the data directory. /// \param type music type. diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 4950e3edf4..f1a40a3f16 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -22,6 +22,8 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwsound/constants.hpp" + #include "class.hpp" namespace @@ -216,8 +218,7 @@ namespace MWGui center(); // Play LevelUp Music - MWBase::Environment::get().getSoundManager()->streamMusic( - "Music/Special/MW_Triumph.mp3", MWSound::MusicType::Special); + MWBase::Environment::get().getSoundManager()->streamMusic(MWSound::triumphMusic, MWSound::MusicType::Special); } void LevelupDialog::onOkButtonClicked(MyGUI::Widget* sender) diff --git a/apps/openmw/mwlua/soundbindings.cpp b/apps/openmw/mwlua/soundbindings.cpp index 57d1e606c1..a5ec69b4fc 100644 --- a/apps/openmw/mwlua/soundbindings.cpp +++ b/apps/openmw/mwlua/soundbindings.cpp @@ -141,7 +141,7 @@ namespace MWLua api["streamMusic"] = [](std::string_view fileName, const sol::optional& options) { auto args = getStreamMusicArgs(options); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->streamMusic(std::string(fileName), MWSound::MusicType::Scripted, args.mFade); + sndMgr->streamMusic(VFS::Path::Normalized(fileName), MWSound::MusicType::Scripted, args.mFade); }; api["say"] diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index addf62df34..32f81c398e 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -39,6 +39,8 @@ #include "../mwrender/vismask.hpp" +#include "../mwsound/constants.hpp" + #include "actor.hpp" #include "actorutil.hpp" #include "aicombataction.hpp" @@ -1798,7 +1800,7 @@ namespace MWMechanics MWBase::Environment::get().getStateManager()->askLoadRecent(); // Play Death Music if it was the player dying MWBase::Environment::get().getSoundManager()->streamMusic( - "Music/Special/MW_Death.mp3", MWSound::MusicType::Special); + MWSound::deathMusic, MWSound::MusicType::Special); } else { diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index ee39860584..79bdf20160 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -63,7 +63,7 @@ namespace MWScript public: void execute(Interpreter::Runtime& runtime) override { - std::string music{ runtime.getStringLiteral(runtime[0].mInteger) }; + const VFS::Path::Normalized music(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); MWBase::Environment::get().getSoundManager()->streamMusic( diff --git a/apps/openmw/mwsound/constants.hpp b/apps/openmw/mwsound/constants.hpp index 5022b142f9..217dd1935e 100644 --- a/apps/openmw/mwsound/constants.hpp +++ b/apps/openmw/mwsound/constants.hpp @@ -7,6 +7,9 @@ namespace MWSound { constexpr VFS::Path::NormalizedView battlePlaylist("battle"); constexpr VFS::Path::NormalizedView explorePlaylist("explore"); + constexpr VFS::Path::NormalizedView titleMusic("music/special/morrowind title.mp3"); + constexpr VFS::Path::NormalizedView triumphMusic("music/special/mw_triumph.mp3"); + constexpr VFS::Path::NormalizedView deathMusic("music/special/mw_death.mp3"); } #endif diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 039c283d7a..bcaec8ddfd 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -252,13 +252,13 @@ namespace MWSound } } - void SoundManager::streamMusicFull(const std::string& filename) + void SoundManager::streamMusicFull(VFS::Path::NormalizedView filename) { if (!mOutput->isInitialized()) return; stopMusic(); - if (filename.empty()) + if (filename.value().empty()) return; Log(Debug::Info) << "Playing \"" << filename << "\""; @@ -267,9 +267,9 @@ namespace MWSound DecoderPtr decoder = getDecoder(); try { - decoder->open(VFS::Path::Normalized(filename)); + decoder->open(filename); } - catch (std::exception& e) + catch (const std::exception& e) { Log(Debug::Error) << "Failed to load audio from \"" << filename << "\": " << e.what(); return; @@ -285,7 +285,7 @@ namespace MWSound mOutput->streamSound(std::move(decoder), mMusic.get()); } - void SoundManager::advanceMusic(const std::string& filename, float fadeOut) + void SoundManager::advanceMusic(VFS::Path::NormalizedView filename, float fadeOut) { if (!isMusicPlaying()) { @@ -304,7 +304,7 @@ namespace MWSound if (playlist == mMusicFiles.end() || playlist->second.empty()) { - advanceMusic(std::string()); + advanceMusic(VFS::Path::NormalizedView()); return; } @@ -338,7 +338,7 @@ namespace MWSound return mMusic && mOutput->isStreamPlaying(mMusic.get()); } - void SoundManager::streamMusic(const std::string& filename, MusicType type, float fade) + void SoundManager::streamMusic(VFS::Path::NormalizedView filename, MusicType type, float fade) { const auto mechanicsManager = MWBase::Environment::get().getMechanicsManager(); @@ -347,10 +347,8 @@ namespace MWSound && type != MusicType::Special) return; - std::string normalizedName = VFS::Path::normalizeFilename(filename); - mechanicsManager->setMusicType(type); - advanceMusic(normalizedName, fade); + advanceMusic(filename, fade); if (type == MWSound::MusicType::Battle) mCurrentPlaylist = battlePlaylist; else if (type == MWSound::MusicType::Explore) @@ -367,8 +365,8 @@ namespace MWSound if (it == mMusicFiles.end()) { std::vector filelist; - constexpr VFS::Path::NormalizedView music("music"); - const VFS::Path::Normalized playlistPath = music / playlist / VFS::Path::NormalizedView(); + const VFS::Path::Normalized playlistPath + = Misc::ResourceHelpers::correctMusicPath(playlist) / VFS::Path::NormalizedView(); for (const auto& name : mVFS->getRecursiveDirectoryIterator(VFS::Path::NormalizedView(playlistPath))) filelist.push_back(name); @@ -1143,10 +1141,10 @@ namespace MWSound if (!mMusic || !mMusic->updateFade(duration) || !mOutput->isStreamPlaying(mMusic.get())) { stopMusic(); - if (!mNextMusic.empty()) + if (!mNextMusic.value().empty()) { streamMusicFull(mNextMusic); - mNextMusic.clear(); + mNextMusic = VFS::Path::Normalized(); } } else @@ -1166,9 +1164,8 @@ namespace MWSound if (isMainMenu && !isMusicPlaying()) { - std::string titlefile = "music/special/morrowind title.mp3"; - if (mVFS->exists(titlefile)) - streamMusic(titlefile, MWSound::MusicType::Special); + if (mVFS->exists(MWSound::titleMusic)) + streamMusic(MWSound::titleMusic, MWSound::MusicType::Special); } updateSounds(duration); diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 7fc7c143d5..1ba80f1d73 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -56,7 +56,7 @@ namespace MWSound std::unordered_map, VFS::Path::Hash, std::equal_to<>> mMusicFiles; std::unordered_map> mMusicToPlay; // A list with music files not yet played - std::string mLastPlayedMusic; // The music file that was last played + VFS::Path::Normalized mLastPlayedMusic; // The music file that was last played WaterSoundUpdater mWaterSoundUpdater; @@ -104,7 +104,7 @@ namespace MWSound Sound* mUnderwaterSound; Sound* mNearWaterSound; - std::string mNextMusic; + VFS::Path::Normalized mNextMusic; bool mPlaybackPaused; RegionSoundSelector mRegionSoundSelector; @@ -125,8 +125,8 @@ namespace MWSound StreamPtr playVoice(DecoderPtr decoder, const osg::Vec3f& pos, bool playlocal); - void streamMusicFull(const std::string& filename); - void advanceMusic(const std::string& filename, float fadeOut = 1.f); + void streamMusicFull(VFS::Path::NormalizedView filename); + void advanceMusic(VFS::Path::NormalizedView filename, float fadeOut = 1.f); void startRandomTitle(); void cull3DSound(SoundBase* sound); @@ -176,7 +176,7 @@ namespace MWSound void stopMusic() override; ///< Stops music if it's playing - void streamMusic(const std::string& filename, MWSound::MusicType type, float fade = 1.f) override; + void streamMusic(VFS::Path::NormalizedView filename, MWSound::MusicType type, float fade = 1.f) override; ///< Play a soundifle /// \param filename name of a sound file in the data directory. /// \param type music type. diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 1eb3800012..d3d75a8176 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -186,11 +186,10 @@ VFS::Path::Normalized Misc::ResourceHelpers::correctSoundPath(VFS::Path::Normali return prefix / resPath; } -std::string Misc::ResourceHelpers::correctMusicPath(std::string_view resPath) +VFS::Path::Normalized Misc::ResourceHelpers::correctMusicPath(VFS::Path::NormalizedView resPath) { - std::string result("music/"); - result += resPath; - return result; + static constexpr VFS::Path::NormalizedView prefix("music"); + return prefix / resPath; } std::string_view Misc::ResourceHelpers::meshPathForESM3(std::string_view resPath) diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index 4e747c54ee..c0728dc442 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -38,11 +38,11 @@ namespace Misc // Adds "meshes\\". std::string correctMeshPath(std::string_view resPath); - // Adds "sound\\". + // Prepends "sound/". VFS::Path::Normalized correctSoundPath(VFS::Path::NormalizedView resPath); - // Adds "music\\". - std::string correctMusicPath(std::string_view resPath); + // Prepends "music/". + VFS::Path::Normalized correctMusicPath(VFS::Path::NormalizedView resPath); // Removes "meshes\\". std::string_view meshPathForESM3(std::string_view resPath); From 884668927f73d7c19a9c84793c236620f4f21ab5 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 20 Apr 2024 21:20:30 +0300 Subject: [PATCH 441/451] BGSM Reader: include cleanup, adjust getFile return result --- components/bgsm/reader.cpp | 1 - components/bgsm/reader.hpp | 5 +---- components/resource/bgsmfilemanager.cpp | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/components/bgsm/reader.cpp b/components/bgsm/reader.cpp index facdee9fb2..eefc8b48b5 100644 --- a/components/bgsm/reader.cpp +++ b/components/bgsm/reader.cpp @@ -4,7 +4,6 @@ #include #include -#include "file.hpp" #include "stream.hpp" namespace Bgsm diff --git a/components/bgsm/reader.hpp b/components/bgsm/reader.hpp index 2d8a0ed481..48508c9143 100644 --- a/components/bgsm/reader.hpp +++ b/components/bgsm/reader.hpp @@ -1,10 +1,7 @@ #ifndef OPENMW_COMPONENTS_BGSM_READER_HPP #define OPENMW_COMPONENTS_BGSM_READER_HPP -#include -#include #include -#include #include @@ -19,7 +16,7 @@ namespace Bgsm public: void parse(Files::IStreamPtr&& stream); - std::unique_ptr& getFile() { return mFile; } + std::unique_ptr getFile() { return std::move(mFile); } }; } #endif diff --git a/components/resource/bgsmfilemanager.cpp b/components/resource/bgsmfilemanager.cpp index 7f749e9453..2d439ccc8a 100644 --- a/components/resource/bgsmfilemanager.cpp +++ b/components/resource/bgsmfilemanager.cpp @@ -43,7 +43,7 @@ namespace Resource { Bgsm::Reader reader; reader.parse(mVFS->get(name)); - Bgsm::MaterialFilePtr file = std::move(reader.getFile()); + Bgsm::MaterialFilePtr file = reader.getFile(); obj = new BgsmFileHolder(file); mCache->addEntryToObjectCache(name.value(), obj); return file; From 2264d067fce43b6b2001cc83e367d063001e0955 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 21 Apr 2024 16:57:33 +0400 Subject: [PATCH 442/451] Clamp widgets coordinates to avoid crashes --- apps/opencs/view/doc/view.cpp | 11 ++++++++++- apps/opencs/view/world/tablesubview.cpp | 10 +++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index f5cdb1b8fc..88a33108c0 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -1110,7 +1110,16 @@ void CSVDoc::View::updateWidth(bool isGrowLimit, int minSubViewWidth) { QRect rect; if (isGrowLimit) - rect = QApplication::screenAt(pos())->geometry(); + { + // Widget position can be negative, we should clamp it. + QPoint position = pos(); + if (position.x() <= 0) + position.setX(0); + if (position.y() <= 0) + position.setY(0); + + rect = QApplication::screenAt(position)->geometry(); + } else rect = desktopRect(); diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 891d954ad4..b48eaec31d 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -78,8 +78,16 @@ CSVWorld::TableSubView::TableSubView( widget->setLayout(layout); setWidget(widget); + + // Widget position can be negative, we should clamp it. + QPoint position = pos(); + if (position.x() <= 0) + position.setX(0); + if (position.y() <= 0) + position.setY(0); + // prefer height of the screen and full width of the table - const QRect rect = QApplication::screenAt(pos())->geometry(); + const QRect rect = QApplication::screenAt(position)->geometry(); int frameHeight = 40; // set a reasonable default QWidget* topLevel = QApplication::topLevelAt(pos()); if (topLevel) From 440dc30ffad981babb1b8f9a9d28e8b82f62d358 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 21 Apr 2024 15:37:02 +0100 Subject: [PATCH 443/451] Run Windows jobs on new Windows Server 2022 images If no other software changed, then the same cache keys will still work. --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index feaccc2540..02f31cea4a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -533,7 +533,7 @@ macOS14_Xcode15_arm64: .Windows_Ninja_Base: tags: - - windows + - saas-windows-medium-amd64 rules: - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" before_script: @@ -656,7 +656,7 @@ macOS14_Xcode15_arm64: .Windows_MSBuild_Base: tags: - - windows + - saas-windows-medium-amd64 rules: - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event" before_script: From da56e1073ef6881518761d3ddbf942b0324ce4ad Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 21 Apr 2024 16:10:36 +0100 Subject: [PATCH 444/451] Try MSVC 2022 Looks like they *did* upgrade MSVC on the new Windows images. --- .gitlab-ci.yml | 60 ++++++++++++++++++++------------------------------ 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 02f31cea4a..406d6411d9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -570,10 +570,10 @@ macOS14_Xcode15_arm64: - $env:CCACHE_BASEDIR = Get-Location - $env:CCACHE_DIR = "$(Get-Location)\ccache" - New-Item -Type Directory -Force -Path $env:CCACHE_DIR - - New-Item -Type File -Force -Path MSVC2019_64_Ninja\.cmake\api\v1\query\codemodel-v2 - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -N -b -t -C $multiview -E + - New-Item -Type File -Force -Path MSVC2022_64_Ninja\.cmake\api\v1\query\codemodel-v2 + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2022 -k -V -N -b -t -C $multiview -E - Get-Volume - - cd MSVC2019_64_Ninja + - cd MSVC2022_64_Ninja - .\ActivateMSVC.ps1 - cmake --build . --config $config - ccache --show-stats -v @@ -583,47 +583,41 @@ macOS14_Xcode15_arm64: - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt + 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} } Push-Location .. ..\CI\Store-Symbols.ps1 if (Test-Path env:AWS_ACCESS_KEY_ID) { aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp --recursive --exclude * --include *.ex_ --include *.dl_ --include *.pd_ .\SymStore s3://openmw-sym } - 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt + 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt Pop-Location Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' + - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - | if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} } - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: ninja-v9 + key: ninja-2022-v9 paths: - ccache - deps - - MSVC2019_64_Ninja/deps/Qt + - MSVC2022_64_Ninja/deps/Qt artifacts: when: always paths: - "*.zip" - "*.log" - - MSVC2019_64_Ninja/*.log - - MSVC2019_64_Ninja/*/*.log - - MSVC2019_64_Ninja/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*/*/*/*.log - - MSVC2019_64_Ninja/*/*/*/*/*/*/*/*.log + - MSVC2022_64_Ninja/*.log + - MSVC2022_64_Ninja/**/*.log # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h @@ -688,9 +682,9 @@ macOS14_Xcode15_arm64: - $time = (Get-Date -Format "HH:mm:ss") - echo ${time} - echo "started by ${GITLAB_USER_NAME}" - - New-Item -Type File -Force -Path MSVC2019_64\.cmake\api\v1\query\codemodel-v2 - - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2019 -k -V -b -t -C $multiview -E - - cd MSVC2019_64 + - New-Item -Type File -Force -Path MSVC2022_64\.cmake\api\v1\query\codemodel-v2 + - sh CI/before_script.msvc.sh -c $config -p Win64 -v 2022 -k -V -b -t -C $multiview -E + - cd MSVC2022_64 - Get-Volume - cmake --build . --config $config - cd $config @@ -699,46 +693,40 @@ macOS14_Xcode15_arm64: - Get-ChildItem -Recurse *.ilk | Remove-Item - | if (Get-ChildItem -Recurse *.pdb) { - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt + 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" '*.pdb' CI-ID.txt if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_symbols.zip"))" s3://openmw-artifacts/${artifactDirectory} } Push-Location .. ..\CI\Store-Symbols.ps1 if (Test-Path env:AWS_ACCESS_KEY_ID) { aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp --recursive --exclude * --include *.ex_ --include *.dl_ --include *.pd_ .\SymStore s3://openmw-sym } - 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt + 7z a -tzip "..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}_${CI_JOB_ID}_sym_store.zip"))" '.\SymStore\*' $config\CI-ID.txt Pop-Location Get-ChildItem -Recurse *.pdb | Remove-Item } - - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' + - 7z a -tzip "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" '*' - | if (Test-Path env:AWS_ACCESS_KEY_ID) { - aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2019_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} + aws --endpoint-url https://rgw.ctrl-c.liu.se s3 cp "..\..\$(Make-SafeFileName("OpenMW_MSVC2022_64_${config}_${CI_COMMIT_REF_NAME}.zip"))" s3://openmw-artifacts/${artifactDirectory} } - if ($executables) { foreach ($exe in $executables.Split(',')) { & .\$exe } } after_script: - Get-Volume - Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log cache: - key: msbuild-v9 + key: msbuild-2022-v9 paths: - deps - - MSVC2019_64/deps/Qt + - MSVC2022_64/deps/Qt artifacts: when: always paths: - "*.zip" - "*.log" - - MSVC2019_64/*.log - - MSVC2019_64/*/*.log - - MSVC2019_64/*/*/*.log - - MSVC2019_64/*/*/*/*.log - - MSVC2019_64/*/*/*/*/*.log - - MSVC2019_64/*/*/*/*/*/*.log - - MSVC2019_64/*/*/*/*/*/*/*.log - - MSVC2019_64/*/*/*/*/*/*/*/*.log + - MSVC2022_64/*.log + - MSVC2022_64/**/*.log # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h From 1bb48bcef74d2b5123812e1b4e1e180fc622696c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 21 Apr 2024 20:56:06 +0400 Subject: [PATCH 445/451] Treat editor icons as scalable --- CI/before_script.msvc.sh | 18 + CMakeLists.txt | 20 +- apps/launcher/CMakeLists.txt | 2 +- apps/opencs/view/doc/startup.cpp | 8 +- apps/opencs/view/doc/startup.hpp | 2 +- apps/opencs/view/render/instancemovemode.cpp | 2 +- apps/opencs/view/widget/scenetoolmode.cpp | 2 +- .../view/widget/scenetoolshapebrush.cpp | 16 +- .../view/widget/scenetooltexturebrush.cpp | 16 +- apps/opencs/view/widget/scenetooltoggle2.cpp | 2 +- apps/opencs/view/world/dragrecordtable.cpp | 2 +- apps/wizard/CMakeLists.txt | 2 +- .../contentselector/view/contentselector.cpp | 2 +- files/opencs/configure.svg | 501 ++++++++++++++++++ files/opencs/{scalable => }/editor-icons.svg | 0 files/opencs/menu-reload.svg | 85 ++- files/opencs/object.svg | 8 +- files/opencs/raster/startup/big/configure.png | Bin 17972 -> 0 bytes .../opencs/raster/startup/small/configure.png | Bin 1450 -> 0 bytes .../raster/startup/small/create-addon.png | Bin 1714 -> 0 bytes .../raster/startup/small/edit-content.png | Bin 2471 -> 0 bytes .../opencs/raster/startup/small/new-game.png | Bin 2122 -> 0 bytes files/opencs/record-edit.svg | 2 +- files/opencs/record-touch.svg | 31 +- files/opencs/resources.qrc | 2 +- files/opencs/run-log.svg | 7 +- files/opencs/scalable/startup/configure.svgz | Bin 5808 -> 0 bytes files/opencs/sound.svg | 14 +- 28 files changed, 669 insertions(+), 75 deletions(-) create mode 100644 files/opencs/configure.svg rename files/opencs/{scalable => }/editor-icons.svg (100%) delete mode 100644 files/opencs/raster/startup/big/configure.png delete mode 100644 files/opencs/raster/startup/small/configure.png delete mode 100644 files/opencs/raster/startup/small/create-addon.png delete mode 100644 files/opencs/raster/startup/small/edit-content.png delete mode 100644 files/opencs/raster/startup/small/new-game.png delete mode 100644 files/opencs/scalable/startup/configure.svgz diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index c2d91ccfd3..2423f0c54f 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -357,6 +357,16 @@ add_qt_image_dlls() { QT_IMAGEFORMATS[$CONFIG]="${QT_IMAGEFORMATS[$CONFIG]} $@" } +declare -A QT_ICONENGINES +QT_ICONENGINES["Release"]="" +QT_ICONENGINES["Debug"]="" +QT_ICONENGINES["RelWithDebInfo"]="" +add_qt_icon_dlls() { + local CONFIG=$1 + shift + QT_ICONENGINES[$CONFIG]="${QT_ICONENGINES[$CONFIG]} $@" +} + if [ -z $PLATFORM ]; then PLATFORM="$(uname -m)" fi @@ -942,6 +952,7 @@ printf "Qt ${QT_VER}... " add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll" add_qt_image_dlls $CONFIGURATION "$(pwd)/plugins/imageformats/qsvg${DLLSUFFIX}.dll" + add_qt_icon_dlls $CONFIGURATION "$(pwd)/plugins/iconengines/qsvgicon${DLLSUFFIX}.dll" done echo Done. } @@ -1143,6 +1154,13 @@ fi cp "$DLL" "${DLL_PREFIX}imageformats" done echo + echo "- Qt Icon Engine DLLs..." + mkdir -p ${DLL_PREFIX}iconengines + for DLL in ${QT_ICONENGINES[$CONFIGURATION]}; do + echo " $(basename $DLL)" + cp "$DLL" "${DLL_PREFIX}iconengines" + done + echo done #fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 074c91344b..b478a799eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -825,6 +825,18 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE) get_filename_component(QT_QMACSTYLE_PLUGIN_NAME "${QT_QMACSTYLE_PLUGIN_PATH}" NAME) configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY) + get_property(QT_QSVG_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgPlugin PROPERTY LOCATION_RELEASE) + get_filename_component(QT_QSVG_PLUGIN_DIR "${QT_QSVG_PLUGIN_PATH}" DIRECTORY) + get_filename_component(QT_QSVG_PLUGIN_GROUP "${QT_QSVG_PLUGIN_DIR}" NAME) + get_filename_component(QT_QSVG_PLUGIN_NAME "${QT_QSVG_PLUGIN_PATH}" NAME) + configure_file("${QT_QSVG_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QSVG_PLUGIN_GROUP}/${QT_QSVG_PLUGIN_NAME}" COPYONLY) + + get_property(QT_QSVG_ICON_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgIconPlugin PROPERTY LOCATION_RELEASE) + get_filename_component(QT_QSVG_ICON_PLUGIN_DIR "${QT_QSVG_ICON_PLUGIN_PATH}" DIRECTORY) + get_filename_component(QT_QSVG_ICON_PLUGIN_GROUP "${QT_QSVG_ICON_PLUGIN_DIR}" NAME) + get_filename_component(QT_QSVG_ICON_PLUGIN_NAME "${QT_QSVG_ICON_PLUGIN_PATH}" NAME) + configure_file("${QT_QSVG_ICON_PLUGIN_PATH}" "${APP_BUNDLE_DIR}/Contents/PlugIns/${QT_QSVG_ICON_PLUGIN_GROUP}/${QT_QSVG_ICON_PLUGIN_NAME}" COPYONLY) + configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${APP_BUNDLE_DIR}/Contents/Resources/qt.conf" COPYONLY) if (BUILD_OPENCS) @@ -832,13 +844,9 @@ if (OPENMW_OSX_DEPLOYMENT AND APPLE) set(OPENCS_BUNDLE_NAME "${OPENCS_BUNDLE_NAME_TMP}.app") configure_file("${QT_COCOA_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_COCOA_PLUGIN_GROUP}/${QT_COCOA_PLUGIN_NAME}" COPYONLY) configure_file("${QT_QMACSTYLE_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QMACSTYLE_PLUGIN_GROUP}/${QT_QMACSTYLE_PLUGIN_NAME}" COPYONLY) - configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY) - - get_property(QT_QSVG_PLUGIN_PATH TARGET Qt${QT_VERSION_MAJOR}::QSvgPlugin PROPERTY LOCATION_RELEASE) - get_filename_component(QT_QSVG_PLUGIN_DIR "${QT_QSVG_PLUGIN_PATH}" DIRECTORY) - get_filename_component(QT_QSVG_PLUGIN_GROUP "${QT_QSVG_PLUGIN_DIR}" NAME) - get_filename_component(QT_QSVG_PLUGIN_NAME "${QT_QSVG_PLUGIN_PATH}" NAME) configure_file("${QT_QSVG_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QSVG_PLUGIN_GROUP}/${QT_QSVG_PLUGIN_NAME}" COPYONLY) + configure_file("${QT_QSVG_ICON_PLUGIN_PATH}" "${OPENCS_BUNDLE_NAME}/Contents/PlugIns/${QT_QSVG_ICON_PLUGIN_GROUP}/${QT_QSVG_ICON_PLUGIN_NAME}" COPYONLY) + configure_file("${OpenMW_SOURCE_DIR}/files/mac/qt.conf" "${OPENCS_BUNDLE_NAME}/Contents/Resources/qt.conf" COPYONLY) endif () install(DIRECTORY "${APP_BUNDLE_DIR}" USE_SOURCE_PERMISSIONS DESTINATION "." COMPONENT Runtime) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index bd6a7062fd..91b985948d 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -83,7 +83,7 @@ target_link_libraries(openmw-launcher components_qt ) -target_link_libraries(openmw-launcher Qt::Widgets Qt::Core) +target_link_libraries(openmw-launcher Qt::Widgets Qt::Core Qt::Svg) if (BUILD_WITH_CODE_COVERAGE) target_compile_options(openmw-launcher PRIVATE --coverage) diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 27463b0456..e6323bf1a1 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -10,7 +10,7 @@ #include #include -QPushButton* CSVDoc::StartupDialogue::addButton(const QString& label, const QIcon& icon) +QPushButton* CSVDoc::StartupDialogue::addButton(const QString& label, const QString& icon) { int column = mColumn--; @@ -39,13 +39,13 @@ QWidget* CSVDoc::StartupDialogue::createButtons() mLayout = new QGridLayout(widget); /// \todo add icons - QPushButton* loadDocument = addButton("Edit A Content File", QIcon(":startup/edit-content")); + QPushButton* loadDocument = addButton("Edit A Content File", ":startup/edit-content"); connect(loadDocument, &QPushButton::clicked, this, &StartupDialogue::loadDocument); - QPushButton* createAddon = addButton("Create A New Addon", QIcon(":startup/create-addon")); + QPushButton* createAddon = addButton("Create A New Addon", ":startup/create-addon"); connect(createAddon, &QPushButton::clicked, this, &StartupDialogue::createAddon); - QPushButton* createGame = addButton("Create A New Game", QIcon(":startup/create-game")); + QPushButton* createGame = addButton("Create A New Game", ":startup/create-game"); connect(createGame, &QPushButton::clicked, this, &StartupDialogue::createGame); for (int i = 0; i < 3; ++i) diff --git a/apps/opencs/view/doc/startup.hpp b/apps/opencs/view/doc/startup.hpp index 061b91b2d1..f2cccfcd38 100644 --- a/apps/opencs/view/doc/startup.hpp +++ b/apps/opencs/view/doc/startup.hpp @@ -20,7 +20,7 @@ namespace CSVDoc int mColumn; QGridLayout* mLayout; - QPushButton* addButton(const QString& label, const QIcon& icon); + QPushButton* addButton(const QString& label, const QString& icon); QWidget* createButtons(); diff --git a/apps/opencs/view/render/instancemovemode.cpp b/apps/opencs/view/render/instancemovemode.cpp index e4004a1537..bc999eb633 100644 --- a/apps/opencs/view/render/instancemovemode.cpp +++ b/apps/opencs/view/render/instancemovemode.cpp @@ -8,7 +8,7 @@ class QWidget; CSVRender::InstanceMoveMode::InstanceMoveMode(QWidget* parent) - : ModeButton(QIcon(QPixmap(":scenetoolbar/transform-move")), + : ModeButton(QIcon(":scenetoolbar/transform-move"), "Move selected instances" "
  • Use {scene-edit-primary} to move instances around freely
  • " "
  • Use {scene-edit-secondary} to move instances around within the grid
  • " diff --git a/apps/opencs/view/widget/scenetoolmode.cpp b/apps/opencs/view/widget/scenetoolmode.cpp index f2795d6de9..1fa7cfb690 100644 --- a/apps/opencs/view/widget/scenetoolmode.cpp +++ b/apps/opencs/view/widget/scenetoolmode.cpp @@ -94,7 +94,7 @@ void CSVWidget::SceneToolMode::showPanel(const QPoint& position) void CSVWidget::SceneToolMode::addButton(const std::string& icon, const std::string& id, const QString& tooltip) { - ModeButton* button = new ModeButton(QIcon(QPixmap(icon.c_str())), tooltip, mPanel); + ModeButton* button = new ModeButton(QIcon(icon.c_str()), tooltip, mPanel); addButton(button, id); } diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp index 57b78ffc71..0e040c2385 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.cpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -60,10 +60,10 @@ CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidge : QFrame(parent, Qt::Popup) , mDocument(document) { - mButtonPoint = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-point")), "", this); - mButtonSquare = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-square")), "", this); - mButtonCircle = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-circle")), "", this); - mButtonCustom = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-custom")), "", this); + mButtonPoint = new QPushButton(QIcon(":scenetoolbar/brush-point"), "", this); + mButtonSquare = new QPushButton(QIcon(":scenetoolbar/brush-square"), "", this); + mButtonCircle = new QPushButton(QIcon(":scenetoolbar/brush-circle"), "", this); + mButtonCustom = new QPushButton(QIcon(":scenetoolbar/brush-custom"), "", this); mSizeSliders = new ShapeBrushSizeControls("Brush size", this); @@ -201,25 +201,25 @@ void CSVWidget::SceneToolShapeBrush::setButtonIcon(CSVWidget::BrushShape brushSh { case BrushShape_Point: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-point"))); + setIcon(QIcon(":scenetoolbar/brush-point")); tooltip += mShapeBrushWindow->toolTipPoint; break; case BrushShape_Square: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-square"))); + setIcon(QIcon(":scenetoolbar/brush-square")); tooltip += mShapeBrushWindow->toolTipSquare; break; case BrushShape_Circle: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-circle"))); + setIcon(QIcon(":scenetoolbar/brush-circle")); tooltip += mShapeBrushWindow->toolTipCircle; break; case BrushShape_Custom: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-custom"))); + setIcon(QIcon(":scenetoolbar/brush-custom")); tooltip += mShapeBrushWindow->toolTipCustom; break; } diff --git a/apps/opencs/view/widget/scenetooltexturebrush.cpp b/apps/opencs/view/widget/scenetooltexturebrush.cpp index 9c3e723009..2e002aaf2e 100644 --- a/apps/opencs/view/widget/scenetooltexturebrush.cpp +++ b/apps/opencs/view/widget/scenetooltexturebrush.cpp @@ -90,10 +90,10 @@ CSVWidget::TextureBrushWindow::TextureBrushWindow(CSMDoc::Document& document, QW mSelectedBrush = new QLabel(QString::fromStdString(mBrushTextureLabel)); } - mButtonPoint = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-point")), "", this); - mButtonSquare = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-square")), "", this); - mButtonCircle = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-circle")), "", this); - mButtonCustom = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-custom")), "", this); + mButtonPoint = new QPushButton(QIcon(":scenetoolbar/brush-point"), "", this); + mButtonSquare = new QPushButton(QIcon(":scenetoolbar/brush-square"), "", this); + mButtonCircle = new QPushButton(QIcon(":scenetoolbar/brush-circle"), "", this); + mButtonCustom = new QPushButton(QIcon(":scenetoolbar/brush-custom"), "", this); mSizeSliders = new BrushSizeControls("Brush size", this); @@ -282,25 +282,25 @@ void CSVWidget::SceneToolTextureBrush::setButtonIcon(CSVWidget::BrushShape brush { case BrushShape_Point: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-point"))); + setIcon(QIcon(":scenetoolbar/brush-point")); tooltip += mTextureBrushWindow->toolTipPoint; break; case BrushShape_Square: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-square"))); + setIcon(QIcon(":scenetoolbar/brush-square")); tooltip += mTextureBrushWindow->toolTipSquare; break; case BrushShape_Circle: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-circle"))); + setIcon(QIcon(":scenetoolbar/brush-circle")); tooltip += mTextureBrushWindow->toolTipCircle; break; case BrushShape_Custom: - setIcon(QIcon(QPixmap(":scenetoolbar/brush-custom"))); + setIcon(QIcon(":scenetoolbar/brush-custom")); tooltip += mTextureBrushWindow->toolTipCustom; break; } diff --git a/apps/opencs/view/widget/scenetooltoggle2.cpp b/apps/opencs/view/widget/scenetooltoggle2.cpp index 8dbd1e804c..44bffa34a6 100644 --- a/apps/opencs/view/widget/scenetooltoggle2.cpp +++ b/apps/opencs/view/widget/scenetooltoggle2.cpp @@ -88,7 +88,7 @@ void CSVWidget::SceneToolToggle2::addButton( stream << mSingleIcon << id; PushButton* button = new PushButton( - QIcon(QPixmap(stream.str().c_str())), PushButton::Type_Toggle, tooltip.isEmpty() ? name : tooltip, mPanel); + QIcon(stream.str().c_str()), PushButton::Type_Toggle, tooltip.isEmpty() ? name : tooltip, mPanel); button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); button->setIconSize(QSize(mIconSize, mIconSize)); diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index 11a0c9a540..a8a3df309c 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -29,7 +29,7 @@ void CSVWorld::DragRecordTable::startDragFromTable(const CSVWorld::DragRecordTab mime->setIndexAtDragStart(index); QDrag* drag = new QDrag(this); drag->setMimeData(mime); - drag->setPixmap(QString::fromUtf8(mime->getIcon().c_str())); + drag->setPixmap(QIcon(mime->getIcon().c_str()).pixmap(QSize(16, 16))); drag->exec(Qt::CopyAction); } diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 3a17210cd3..444290f3a6 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -85,7 +85,7 @@ target_link_libraries(openmw-wizard components_qt ) -target_link_libraries(openmw-wizard Qt::Widgets Qt::Core) +target_link_libraries(openmw-wizard Qt::Widgets Qt::Core Qt::Svg) if (OPENMW_USE_UNSHIELD) target_link_libraries(openmw-wizard ${LIBUNSHIELD_LIBRARIES}) diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index a3fd224390..ab12f45fd7 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -31,7 +31,7 @@ ContentSelectorView::ContentSelector::~ContentSelector() = default; void ContentSelectorView::ContentSelector::buildContentModel(bool showOMWScripts) { - QIcon warningIcon(ui->addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(QSize(16, 15))); + QIcon warningIcon(ui->addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning)); mContentModel = new ContentSelectorModel::ContentModel(this, warningIcon, showOMWScripts); } diff --git a/files/opencs/configure.svg b/files/opencs/configure.svg new file mode 100644 index 0000000000..91a5efca5c --- /dev/null +++ b/files/opencs/configure.svg @@ -0,0 +1,501 @@ + + + +image/svg+xml diff --git a/files/opencs/scalable/editor-icons.svg b/files/opencs/editor-icons.svg similarity index 100% rename from files/opencs/scalable/editor-icons.svg rename to files/opencs/editor-icons.svg diff --git a/files/opencs/menu-reload.svg b/files/opencs/menu-reload.svg index aef27dd104..32cefa7548 100644 --- a/files/opencs/menu-reload.svg +++ b/files/opencs/menu-reload.svg @@ -89,23 +89,82 @@ x2="76.100182" y2="249" gradientUnits="userSpaceOnUse" /> + + + + + + + + + + - + d="m 52.8211,61.122306 -2.004035,-0.0036 0.217103,-0.201119 c 0.158097,-0.150209 -0.07905,-0.375524 -0.237144,-0.225315 l -0.50101,0.476016 c -0.06587,0.06207 -0.06587,0.163247 0,0.225315 l 0.50101,0.476016 c 0.06533,0.06258 0.171817,0.06258 0.237144,0 0.06587,-0.06207 0.06585,-0.163246 -1.8e-5,-0.225315 L 50.817065,61.383321 H 52.8211 c 0.143894,-0.0027 0.357404,0.162378 0.360144,0.299094 v 0.391921 c 0,0.211563 0.264583,0.211563 0.264583,0 v -0.391921 c -0.0028,-0.311974 -0.296373,-0.566316 -0.624727,-0.563677 z m 0.07432,1.891352 c -0.158096,-0.149435 -0.394425,0.07511 -0.237143,0.225315 l 0.2171,0.261015 h -2.004033 c -0.143895,0.0027 -0.333193,-0.162379 -0.335933,-0.299097 v -0.39192 c -5.38e-4,-0.21105 -0.264046,-0.21105 -0.264584,0 v 0.39192 c 0.0028,0.311975 0.272162,0.56632 0.600517,0.56368 h 2.004035 l -0.217101,0.20112 c -0.06587,0.06207 -0.06587,0.163245 0,0.225314 0.06533,0.06258 0.171816,0.06258 0.237144,0 l 0.501007,-0.476016 c 0.06587,-0.06207 0.06587,-0.163245 0,-0.225314 z" + id="path1" + style="display:inline;overflow:hidden;fill:#4d4d4d;fill-opacity:1;stroke-width:0.00381524" /> diff --git a/files/opencs/object.svg b/files/opencs/object.svg index 717269b9d6..8f9261fd18 100644 --- a/files/opencs/object.svg +++ b/files/opencs/object.svg @@ -108,11 +108,11 @@ style="display:inline" transform="matrix(3.7795278,0,0,3.77117,-148,-187.58427)"> + cy="51.863018" + rx="1.7196451" + ry="1.7237496" /> diff --git a/files/opencs/raster/startup/big/configure.png b/files/opencs/raster/startup/big/configure.png deleted file mode 100644 index f0be888a15532311418c82714d7cb56dd5620a09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17972 zcmXwB2Rzm9_x~6nBg!5j2}Ke@$hA^RW*Lb~8QDbH_gcv)gh<9U3L&?QkR4KFlaO^K zD`cd>0G&+){QqJ1t7e`E!iy*WM`6kHoDaUp?RDAI%gD{n%iH>aE#mF% zE$QIu2dS!GJV9?2k{~9?%1BKc`t6(cfrr9)Sek}HR-_egXrmBIpAKTgBsqc5_>Z;^ zk6D&ou68uSVE@)N-IsM-uyAPKtCF;M_2Nol&qD3;j4!T&@npNSSmH`&{Hg1Vj7>K` zNGPRPzx*cAu4^nH!)Wxa#QCZ1kY%{FI#pM#M0@yGnf8)V#-_hkW}9gEWbx5?D1wty zgDFUwe`P1&1!L2lhLXRJCE7FErM|Nz**;@z3bHkOFTwC2ZK+)L;73Qv-#m%-x>l+0 z(G{bQMnoTzI{oyiGM_R8sNi@Td=W|N_d(V#Sv`;X#SjMl_vzY8&hapYA5_ofrfzan zUSxavxDKJWt^BtWkbp=~{yUDKsjQH=((Wg!L@TpLB?FF^Ms1`oaFpCA>t+m+&V`?| zLz1cfQC89+g`_RYNP)|jFXK$#{fI8ES#8@?FS~vwpF;l&wF++8W<8qZhp?`9o(|h) zqMwczn@{TLNWCd?p`=P=Cn$i}^oDbNzqC%O7PocDkV9s`7L4lNQ zTjFL+OpF-=hXhv-ffkOfr2xm)L}ID7wIr9Xi(+zmB6>?6uq7upOh0N`au2cQ8XZrF z-ga_y)HgHZc6M-hTQw2FpjGqV=8r~o&B#zx(l%fbshQQWn}l;I`cVautOmC&voyE7 zY{W51wjR54c}%A{A;f!J@noVz9Qzjf|(- z0||{Dl2=Q@?vOf5+3TAo8xC>aLDX16o;`bZ!5$a5nSTtWnn!7*MW=$p(>Gj4I=Q~@ zafhqf4rm70{)l|3f14hOv*j+42tCAbcqm2nBLOF6P01l)8ZMF8Jj$J^fJX?(_pqOlrv!!9A8ZE#mvVJutdj9#|SmV3a zJv_|g?Ce~0aiOSG>)n4Mrv$aa&`+rS{yBc`9KNB5RZrc)?9Ca%y>5}XXJnoI)0rM^ zq<4vZadEM6cX#&{cCP}Bce5QMId6jIDGH!L))DgWub#CwDjB{)H_}6n(KB9Tg>oB?Y+i zJ?ZT1v@$X>>KLpiZJa+zwzN~Rj739O0aXLJ8IzkgCq69{Ysm6kzJ8qn$wd=e9Vm}4 zx+uJNV-D&k;PdkvepPu|m*4Ig7+`mHb>$xzAl>X15LmFUIrO>l>{aPUoeCM6!yWAuP|%=3hJ@uC*5Zr!>}IX4Wid zCX$EJl;Bl&JX+X6)33zx-_pbf(mas)Ptt#{pT(38JteCSI2@MCM@L6T8?B^FJtrS? z#yG4{g_^pnW!Xf-wM9v^?uv_xujB~q(`n@#-PqWOXr1D&_(N$U6-QwD(FK& zC|l2^vlYKW3JT7Ish=n-FF$pQNPCacB0c?ZLO>be+_`hD&5^8odDPdBfhmR5*4AD) z=`l{43^_60MQhBZl%CiTs2=%_~p%imwUoyLif@^`kZo34_$046#^n_ zw9-GS6er5bt7w=6auKkYH%4u|0xZ}AoU=nM-Le-Cm zX|xL==x<)V4+kysoV$kx1`Z*IX^`KpluS%avvYIW$La?hTfz>rCaxiZ9MOkzdhS1i zTOa)2F(QJiS4n63d|O&tw!g^lR&2+%PSHJ9*CvSCI8l~Jj z3H@MN^yigRG4~dMc2-lct$UJ6>QtY${HRxgke&4JJd0;}7Abl1_UQdPXf&SdbFvcE z$P1E+Z}Z&`aC-hh0=m@h#!wS8s>sFx_TJjj@M!Gd`CLtcuHs<`vQ&s>j=?C&uUJGd zD*M>}Kp-Z%C7TY-c9||jP%EcLhC}j>f@@2?>*%L>A7xUKSd_{t&myIMc!(2{Xs_td zlYP1Ee9B^;R{Fe?_P}v4>t!5OCeOS~YR9!UJr?T8&CAgo$J;idpRrr|&Ao9%c>VqS za=(`RrGC$r$&J|FH%*%hYnQBMbgkf3#jcw$56oTObCwAP-xY>?rj;Yz_(bynUQEiq z98FUE`%X&Tclw1%!SN%lq7bZ%XdY?4+HIg>PCEkddv1U-SX9zdKJL5RUtA{`EK<@^3mfKi-QyNDnL~Cb_H! zXU#>NphfAse$g)2oMQ}1Oa2DdrgS1ITL`o2#`6lhnteTCC7jrq=`&qqZ?-43@-OOyRDeEGmdmBu>)cqP^*_V&v@mH_0kkR{FroC7!TaCSYKbaIR^50^xq2Y{ixZFUZuI$35i(IK+j85 z2#r1EqiTyC<&6Mrh*;Fr-Hi6!8>t0_g@sNRV>~hc8fovlxO8X{<7|h{OY%fWOMDll z^&`l0F^Wk_>iSR}bu74&8>u?n^?qdWFfK1+>s*wOra^N~tcssj9j^$^d8p*gC;0eS zS^0BkX9ky3Tr6q6qLE}v+kuE(oLbxALh>kg3HDqxc{q%pVVNHL{=o0}!}&4@*bp(; zsZ)uzq5i>Mcur`n%5J`2TjJ?**NPGH%>=Bpa8^YK#0y`1OQBEG#E9Ja#O{aQ8y<`w zOMFIw45fl#1^a@gR(w8R1o@!9a#@XhbQv(p(|tGXJ7#8L@}whGe&!736+GSHFxT8w z;;g=b?$Cb~H0t^UT-qlV@H@}13Qwk)N<6}BqMf|nyNrCCcj(I<;9zIJXny22z?tT|n|Dtv3bE2Te)^ z_x2i2^Hq$zdrHH?%4N#rV{&5HA%CdG*YhEKob6icbXR!Fi4K4ucKL_cR*0MVXO+ zsq9UY&fYMgE(0B!DXc~`TqtQmDcITB+ZL3&#J6@&iU`tC`uY0{w84spmh1LhANuofsw!CC z=Y<4OJ>4@jkG#D4i|$lCu&f(d@bTGOtSD@6ZzrpK{2qS*BoStj=y`mgO#v-HtKT$D zQm0b15DAX!y?fz()L`I=uE=@n)Q+V7joH6{1Eg^-50#yNzR&tpR#rwfyi?lhfq|Dk zx{khju}uL1%+`qUHGnIoB-<1(g8dN5!uP^*4{YgG!)xt?#6eJ#Ts>UIO-9IgQtIxu zM4|swcRZNh7)0YUq^v`PND%Fvhs(5wmIV{Y>s_}zoNvTThHJ3mJ)A6>? zS2$V#5t?+DYYGAv^z@%*K4kG%av znGRTI`2>yBFA;r9%Oe#dOptyid#f(%)!yt_ z3wSr+_z*7IR3aPvK{m zI#N8(;BSTLL?d`th81PJu=QZEg$drGdb56Y=+Rr#3RjEr_G!cBb99s+-Ns{zi;LAF z(Z?>Yhx_$)Qp5?*%^ncD4(LE87d^m{YD=`!^bJs~Gpbt9@#(ToC>(k7<_)}{?OtQZ zp|;VG{*;Cp9JVfC^wUFbH%IBTntz`e2KZVBZcA{8?vSbLpR`j$Y!}f@dl`2~CqtRr#M0N+&_s~u+1>V}v);vKr6Tw(X4C3N zF6E=H_zNjI*M7U*zpok?7n%l36RPmw_tk4FxPLE(ChoX?En(!45WcEy#?g(U zh_JD8Xs%yAhuvv6AXh+G-b?sP{inESw7?z7GBO%%u{b8q|W#Uc~#3W;SnFWOGj zL4c_QN0E7jwmeR~@mc)7{6MRW1 zykd(xd?ywp=)bkpGM9(T_*Vy3UiR_hk)emb9Vsg#YQtSuAM{_hPoVQ*fQ}6c^+CSOqg;hq0BrBWZbx_*X~p*Wi=l&Q2XN za1&Qc|o)uS8DSC0Wi>7r2u|afnMY{WcLJ4RbyWVqUs}TQ$ zgW_~3VFq>J8(}-NF{eev6|QDsao0P=dY5xCpCS0M7$Z2s%2C&s9e-XsTYU~YOs==- zs_fXjxtdQ>uFcLG`>+{!>Cz>@_Sys+E*cppyVEkn-F;Q#;~Jh4qUHlIJ66vzG_9WLu{6?Nf|p|K>nJG_zja&?vMfQzkU^Q1^c~jxcecqpnx7y3&>RAkP$6+S*TCEIT6<-ni7HxZ2dFshE-SbFx;O+yVE`fi|*a(%5piKNS!2McFfvrQy|6e5N7q8sJ18N z*Sf&r^?H_3iU+l@4TX(im+biM_t|LEiv^C}(Y9ugV~8 z9$;HWj7<_C@!9$L&yAt9=kI-CoDO7cvikEqrL86?%T6jKNLaf~Zpzly_P&!-Q8vX4IUq$$qzs(Wk9@%-XqT~ooEH*bc;$H!0I7EX+5e1)Xs%UFM=W`m9Z z3w2pVMNWA+JJ=%JazA@z2BMvw1!C9DlbIavCkQ+}>{V8gQ}y;dz^IAmH^);TaCG}9 zXW$?3ZD~ny$-46;KYD1T%-^~+T7BNWGc9cVyyqVsDAh2{z0Z6F`Cewdsp;+8Q4rIh z{-{C;1+)mQ9JtE}O(F8%Eq9@c1YZCBkn{W7@7nGJd1VU}9sXOeFT}edPx&uzw~p)r zK2NQrefIc8lQ9z=Q7O%Im|xk4g1@Hp zwV2~d|DL%8oSfY1NV~`iMcUbg>t_s^xdCB^dClB^{BCR5>W0-|XIJS_?4_XAUJKL( z-km99V*=yOk}}URdIv*wk^wYF)A#U^}V-kPqJR1kL#k)sy{4y zK`t}r8Ljg^bmc((cd&S7NP5LEdT6Uk@NQL>AAJ?kXwVt4bzk7Uq(oovtHdW zo9pb@+`5mxzM-?d-lx~y`zukKh`d)G=*ICV9*u~H=p^%Es?ysh1DAM>sNB^w={_Nj z-l2c0b?@3rLh2(;p`(9?%f;tPp-Tg5|1|6ys!Erm&5powBKe!TqtW$^tKPb~-^Kbo zEk>>TkEArj|eEcHq{7>;IJAHoQjad~H-wEvc5!1Rtn-;l84BXUHOaBF_aJjZ{v_v0!c2mmirF0f>tppj_rqH5s(E|ML+XA^1!rrP=JAEQ z%`dpoi?+n#iYnLJ(O}44v-9#qc~=IhOq1FVWT2uI|IbHxnu>sX5xvcPT#*{inc6{9 zI>eaT5pE^*(vbN3CpbYsfDn7NrjoVaHd?8IUkYgygi>Yi!Q5;_r^5@SV z=N19V_YKQi0-f`BFM#PcOs!O2jPVbh*^!AcAu{&Wt7KSnK^|(=`&t_?C?qPA?p#_q z=iiX~HPmWBAa&tmaJSx{KcC3NY8;6_eZPW@cTuGK=g$b}9GDIHNieKjM~XN1_62Mn zK3CMI;+pYM=8u`G9Q4gVIpsII$)#FLr!+`04i69O8yHXk&W0N{l}Ius+K5YdV)(zq;0)a<6oB%M46@M-axR0n(yJj`_MOpQpM-{ve|`& z;#t?FhIOpKf%-tftTg?%hR@ICtpn-8=xg3gW&td=j9@K)`C8x2_>yWSkD+|!Qgk2& zDL@U(>VqjubcFi*B$Kq@UO@Qm9DYkt;%G@2QPExYpwmfMm)5`g5qBAR!oP+ zR>6aNM+~LD3u88%pePP;?8meBP~iVrk41rrX_Xyrp7MZN*&%^|e`;+?)NdyF>&LO< z`uk@=`t~4whE|VrE6P{{l@>QBw#=-qKCms6*ckPdj>Hk2rBkGwen{xNlv3ML`dCph z)t#jQQR!qSwWE{M%I*R_=VXgTK%XE*<H4h|CF(6eM*A= z&joNe(X3|(otdKbxkGBI-rf}@T^Ny-!exSyrDcqf?!{BqorqeJt*oqU=9@Q%KA1XR zy#dYhBBmha;gJz23j6*2RV9;}9#E!9K?EH|$@gPf`;jk)`D0s~J$|hzcVYA52%-cT zxB|s=D>Dv2W$)g-gScrHjjN$SL~#Mqb{(%?NIf&Xp}?ngPho8Iifj2MQyVVQymxMv0< z^O-M`Mk*X)g+!}Ip>IaLt8kRSEN6^Z6kt>`%+ShdTeULegpTd)MzeRu-(Q}m>dVu+ z0=2N)GTqt5!&LgiSM~*G0MAWAv!eOOJM$A!cdvc0KTV=RP~i3POq&OnIN>-3G|lxP zGdsLmq1%@YG$CIWdQO+!BldYx>J!N1nHKFS%;ExgX)-cY_AQYXe}2dQV?uyU7533x zJw0YH3v%An|Ko=p`&=nY{Zo?Cw!{_)tC}H--VwP6$zGE?-cpxH`Jdy@ z)rcpMEsAX{rXH2iRnJN}%53$t(c@QW&Ml%%p=4-dW(0a4x}cKD*N61I*rHLC6Y%p8 zW`6`6Jj>7GZs7X7ut8#mQ}Dhca8gr1c5>g*@fvs#IE?8#W5A&et`)IzD-1{m@=vLL z9F2~sKYAASNN0)PidF6!znIiAJ~;SfY|PTOSk=Mdbgpjt8KrbxyRhi`;|kBc;U10wmXhyNmWOMNn40A z{lsIPtyyulenjgcysJXF>%fsO3PnFLP8HnXxB3}PTIEcYXg9eUzfNVAbS~a@=%xKi zbiA<33tL~H)L`MV{8x!HaWb&Lc~~GB7Qny)j1z6Dg8#G%K2{x&_M%T|E1D!tuR$+G zFaEWWt(n~uHR2T;GmKu+^hUgJ?aFLU>?6iC>|UHYlT{*N!*BVIFR9K$jHH5$AEu@> z8{elvmTI>wA%B83GR|uE%9qllu<~C-BFR8axy*daoHT&5KjYL>%xX**BbFm#Yjht%t{4ir4$QY zfI(cifG9lI2+ukBWMJDCAMuv3A3*tLg?!$!=z8u3SS9nLAQLJE+<^LuU#-cS^1;)Q zMLAeSJ`+tmiQyPECBL8X(%5SPBIc3*FyySDRurddK_I;=OAumn(cT z&`L*I(6kfEXGlbKd1`Joc=2`cU)8W+tjJ1qwoeTK6-N4sHi4bBc1t$UH7;yOPWX~+ zD@I&^XJm93#k-O)NOPLBKg)UWAw-s_@Z=GN76L*E$e2O%JRyvSlFD#V^{V<*Vp8zc zwBWC9y!HhAAt~67EZghJ(?~uA8ykL5DS2jR1G+ZnJDU#)$jNf|RE={OUm` zQe7)j1?&mqQKZM0a{Or1Kajr%YN$8qiwPZBl;nZ$g^^CxS{!olxppm|;`d=#RH$ps zt!i{B9v0o|sZY5w=IW#Ujo%UR1^dH1p4q8@8ur|MwtBIsQ1*k7+3_#pJ|&({!FbXV@4XweT?ltFNL8uKLKnu+`!BHw zf|Cx&{c8@R# zN~3A6{TCY;kYX^UdyfS6KhRhlQEvYAp#5yZFW0SV*9!lF_URC4EQO)PR(PLjy?4fz z$Uh}8Bc@z*)M2nETm5cED>}0h8|^pk)5Eims_g+{lK+glDx`OQkR)9|T0H-F;i?S3HO8m z0M7zyY9t5x?B;PO4is(+Q2gst6;$1e%i(fFS{s2B7zItG@kdp6MDvc-ci0&fx|QbX;N8O4&Y8Vxc1{*T1{LtqZF1 z75cHWUR8J8Do2-O)|;0fR9RJwyqHx97dj9C@3H#1Owkso9dT5s{W-lwNQ?2+cZy3( zj@EygxY8YkKD!wZg=!5M$HNY>f9hLKH!+>1wEG6^kGtY$&g|y3Prtg}te5f4<5L+< z84->gzb`97hY}z_`}1M*K{WVa^kavIZSEROf()*;8^+fS40`;M+aJQY=I=q>?$|*IJd+2zIwhb_^|fKKV%+sn^(g++o#LJO`&}M_?9U|*e`QuRAvUv>sf+fE zIm?M^VuXm4n>)71bvV8TZ0?#$*5wAaJDwdEw5mtzI;S646y4Ywc#gJM6b;0LC@v0a z1wI^xRDpekl?r&0Ut92;aJJch`>~ z>VS$Q{l3Ud;G`ge!&QEvPX0`*E>Y$jTR*|ZEb4UC4VCSOVDIXUc z8b6U|F(LDvz}a1&wlxF-d=m$H0?t4h-cfilW{-bvrjP*x+O)X?r@Pb%$B5k4#-Yqt z5^k7>XH9U@y1NLn_|!?g(>*QQk940St*)-#f@3K@M4``OYE=$47e`ok_ku9PxPpm1 zlSLEGgEGP(YoOwaQ%=1W@jQ|NR|i&@E-jQvgJfoB6WriwTE|8}3a1$K^Y(>TSyxzl z-|XL(xoWZ~%8h96Q3%roMl=f?`Zo%XI%3RUN%wMX(fo@>6A1>%?HecG%?=PuIEnqw z1+FfwHk$AMFQZC3JNM3l?iaimRtgcD-*J;0V zboa^wNzT2bI}DiNS!^0%S>u37Y3z(nw2>Ci)DHx+0dCJIwQJrxRF+5}*K zbv=BtYj_!3TZS4?0D71{rsC5uo}+H}d_)NP+^*ZehN~s-E9q?RWEqO(s+kPb13vF+ zxoOc>N2rNJL*3LGT0(7mMmJWqFVvc(ZZ34J_VLd5v8q&E$7gI0^q^0Y_>pA&6{E7J z)r=Dvuy5dPjlSjF+1LeT#j_u*yxt#TBbGT}p=<$}DRdx9*s+|uE%m1kz^Lb*ic^`^ z1?((?KNS-;#Xajq1XGjS3u_FQ$#-l zTtq!o3?|)8$y_2Y!ZPx&18z*aO)KlE|Ch%U66iW2B-WV1jL8`<#TT>q<1^zGZaA_^ ze_ZKJoy?0NhHCHiMdCCM?#mX4l8g}su)dicC)>9BHs5(0M!m;tpH5Z!56smmE{Z>%wXvbbLM=qn zLO8)JM|WHt+V0zaKSDJIT(0YBx-+lY{cgBc*!T#RXvL4UOU^H<^a<0QJ z9G&Yy@*6`0b6SR!~n8((c{SQ`!05NO|F+Et^a(qt#G} zZ8VfD6Q>l6d3TNjjh^{k^JGf!@0+-=8=7Mf_GdPG`PH z1#(g>pcBG>#?Ii)KfV)lJ-F_&${Oks|%+teErcl7zg7xV0`W$XXqmS>D z4`7b%Y#49Y%6t<|ovVFYuz#ENt1u?L42u(sTEhjbeha6RmA7R)KhHGLskB@(Jm*#X zNP$n}-FA5(nJ7Kc5DYgQ=knVmZ~v{`mIDI@)U_seajmc~;N3QDFKIMeEaTvJf}Y%T z#hcUjo*TFzsDqs@yq99So>F?J@Cuj^)O=otpj})RJvMe^&dv=)6W{h?q?`1_{c69W z;x?BPh37(`n_tdW)N}e?lz|g+Jy~*JuGOPUFtTr=vWO1)q85?oOE zzj6^(dW`1mJf`j=gOniYoQ+wX)pXknm&XnsZKgIic^Z1(TtDZbnBH}f??^N-b^7MQ zMqTwzlc;Eq2uI-JoPO(71lxma#V*ay8w%AABV{iz(gU0gc`H7|&jW=fv}?cm6VG?% z%LFMcRZgYq=B*ui0;wrMk8_8aU->25PPq)x@W#ds`C9n_JN~)sI_PHz zE(nr}$v4a>Q^067*ZXEhi7)0M^e1wc)_{EabMX;$UZ(FH+woo=OP5|9CY)l;M!F!WFZ>ZacaVdD=sJB@qmaqsWtBa3)N zz9V^`@OXCAiCe$Bgh566Tt?Hc59`GYcVnhm>Tq|WM{9(9 z(1=_8zRr`Tl~+xZ6A=^IZMcH2di$=7M2*0=yZiH}YF~>f+6GCu)Ja}VbnAclOL5K1> zsCg7|ct$Pa04LFINrD1?>)P1QM343e*`N{E>P4ICQBqQeow(3v6Vz8b{;;Me%nMPd zTji*}c+15!Ozo|}w_$p{7{f(iqC)en&69qm{;pgq-fw&I$>W*}(vL%q-bcvOA(3*a zi^r=+Ewi5y{rdc&iXB$#zh!r!6CY^INi=*n->16@S6cD$BM_s{sEhJ?JC+7htl*uT zoooE|cQ@{y7)h5_;WnUgUwBvXgE0ad(qVT!V%HH&nlZ^Xp;@n63u8#teSNR|P`wVX zPp%9Cxc1*hFXqKIZh3BQM}-~rM*k@nRpoS&x!v&M=9aUZc?v91u$c;dQ^y0+{+^8S zUiovB#W2p5s;g0xbTgJ>1vS#wR|=TtDi{g9Z5USrKj z`kX9$7#dtr+XFPq72$7I?CMhY2<1*3jIj%-7Zu7vlC5pXbrvA<8#P#2!2=rj3 z8}EJyuqqwOO^Co_?w>pW;zX|&$;*>y^k#oJ8)rF;J42;^1>^F1ke@l?fW$6t{40gL zi4tKv&3m5}!m-jJVipL*q5O72FUIA!-YJroM&NH#x2-W9`NgZJ{2ru9E)LpJQU#Ls zjEhV?^i)P(s(*W2!`;v!KmHvw;hAw^xz<^qhv2)nuf*^*LZ7ua3N^rpT2#P(DigSB z@z3bs7m)V^F)qV+xiE^A0}Py@zRhIMP%QMsIr1j%CV#UTzW>404#4g2l-K(Z%fv}! z$Y8wT(7HpLx|x96XvQb+7WR_R@9tEsZtdJRr=RxWdPG$TgHdTB2@W~H&@jVy;Vph9 zoLS8)T+cJ_ZI{x~b*SH*ftiBWA-InRdX~|xQzgRM^`<~N32YiIW^2=zDx#U#@LM!4 z+z5>7T$eqNrI%AW{`8f79*kL|YJmD72*hGC_BC#R+SF3LGf7bs*2@qMgMks^N-6 zbDWhPasF7*VYik7xYXp|!s6lpJ62AH*xwWYeTJEBRzs$>=U1S$V@wczj!v3RCGX>> ze#Obh@yy}<2++mvKTu5PeX2Ee$K|)E!zUiIBm)zZJU-T}+%v1T$FKz(M1W$NM9uYK zmHJN{f^@*!CEO@9!DH@!Nw9y5Fu&$@F+JHCdz%!B;lSoPD?8g~%}PDvX`sZmTcO#yIE zt-~s)tg0%rx#R?wn)U2jMOJ;tGT2EsJt1enY;>y3e;xnzD=ls{&yOyIC0W>^XMUg% z#~t-CN1y(i2X6zAtjvJ#GiD`qC;!G)?FU~ z&pSh#sZ^6E7yRds-J34FDCQxPDofZ)iS|LfW$kX&l#4vQt+4wYn)hCFhF=`Z>uh9s z&WVswFd3hDVJH41J8*8qZ4e=L4Mm`pI`LbeM7mK#z!k$R)M^oJB>|(bH?%^AT#mlh zu~507H`kM;fu6X)NYi91Je&tV`b}P#6$ye4;f6l$(Jh=`#JN6U+#N6qisq>mI;4yI zyh`V0uD%u=y9N{9N9$*Zq9}Pq722BR_gQ5$Mp^_42 zUFY0_zd!+HOiJ8lPk(Shb?&3TZ4mii?oIRv1L%|C%aKxhW+16`4-C90JDdCokNy4iC^Tae zlae;k&4Rro9T;{Z$A#3T1l%FD+2_J@BVEdC-}f|zhuPZbPC8LlPKNUWz(3Hxgu`() z^yFLtLN^-vpJ#+bN>{&{)_$_Gm)*oV+uPgsW{|3)(B}V)&aIqkg_#BNHd!z{E8guT z#CP@?zIJ{8WG_CJe(<#oDXu5&J^_+Yq~c=5?RPLGwf(P&xu?532-q(_fn&;?l$aRs z-Q(AhaO-)#BUjiOoYh!#BU|p~LEj8VCbb&!27j{&k!YV?STJ&>lr~){gx(ep56>~M zkqUUC^n*98j`D(mZsWG^f$xvpMIC$Hmu8d22#X#rZ z8eprxo4)%5Hu|S6BO?RF2U!46$d1`((!3WrR{6p2=_*h%B#1}74r#{YS zQq?2Q^&UX!1?-cfE`yscq?>*sq}*w6i7RSZBkQS%7t_SB6TViIKX&~3$0ZS^?FW2= zw+#$D-V81r@1-1q2{mjD_5cH=R%hsT`(&8p9>hzk=6=O4 z_>({&)lOpd%#WpSUzHA8z}J(0xhvE(I+nlce?C6v6%GWdKj$oKd`#(W1hB-F4anI1 z<895pOuA>5mEGi)6MlW{(%ap_lc=M0mi@6nzPkJ&()y(*FoLVy6zX7Eo~u$pPQ_KZ znrtoUS~(x?oQ2U?LA_V9b9pd1ws5k^_dE`>I~NMsEc&h#mtcE|wAojv+8wqG5Piu-IhWsCiwQXrQ&l%&$epQv-s-DjGh(vJW0cKPQ??PJjHRWbW;w}pGB zG81MR?sox?$_bKTK|0XZYaZQ>?2aWd!m<`xsf%@kv$fA%I zOO~!pqIHA7!FKClM7Bb#&gutK33&WxU0}6O&t=X)c{C@~@rv(q*6{1<}qo@(Pz${wJ3`DYx5!I_V#d#(!BY@If=PTh}F{g0cmSD z0Luwpy@?Vii0#_LdP!?bz!!kN{=&}q!T$K@PF+4QTis?9ieq0QZwXF)M4eP`V8PS^ zV{?h&Tupc4^0HdiQ?IAk?IwYu5W(*TS<_^!v7nI}z`9<0P-%Q)x#^~Fwd-;?F3{beJa^34yEc|emc(Vm^3Kf9rB z@e)82FcrE)G%ynFXC9*-%B!ZH$fw6BL4drt@Z%i=$Gj3ee>_K`ef$J@P$Em84jP-# zE!NHR^vsf!;E#1Xc?;5SK+3Gy^@_tzxKrA@@TT-ND4=S8fR(Va{n1DHhymM+w+(Yb_1B0v2w`JuU8r%Rd?#>23|dLHqnbpT`R3Z%%JgJEEB9MO*d_(8&dto>a5FO&QQ2wSX0Vo{RFwX7Q~)V7Dl zVA@XibX)gkB!V1DA^%>0{>P7-CRroiUpCMgE;reeh$0v#eDhiYR=#G7UY5;a-slzu zAq)~l!No`uk?r#+OHzj=4FB8=JMX*iCnBibEAphtV+=)FiFMB^PFClB_8!rE$f;V3 zL(B4{o8&VrEG=cA;(myZ<^vrKdXrEsAgT(oob4|+6w6_Ukv~T5c$*VuK?9DLWqBS0 zZN=Q&yiuAQXz|aLXdU);f7=5u_bBxw0p`DCEb|v$>ubpr_1u4&Uyaz5>H$XtdiexS zOP?&Uan6nHsaO+Wv2nJS6jeV5s|b~7cheGj9-R+5+t~D~boDd&AQSbh?l*3AJGqH?r)>Q%XNo%Ps^$5Pv^OK) z?PSt)^X^iDrH6xSs2M$`bS$z428HZa^!&O1b77@LZ+2zmP9{2a&~$wBqfA&Je64ZZApTr_Xqyt#Hf$m>wIt1oS~t%~1;*Nc4D|Q=00TreKdZ8p-~W3J6gkRa zG`UvxSmeuc-hfv+xmU}M|kpMgWjubhJG*%w;!Y~M)u zjJS~)n|g10Fu&hy7OkA~Pj}u{kC)Q6^uQnvB3)Josw_d&+%r0xu5jRtiLvpIC=Lh2|25hV)H0857Jz~Hm>gnraeW;iY{qZf8^w#AYS^9B}2tq!2)gu^N z5?@$Y=&(t(WbA#Y`e}rl z2|+-wn(KhxAYS1z^2ox;sZcHb2f3E{&iK7Vq4U^j?f3`D89>Q|SvnY?2h4Oe9(asD z7Q7S>Dq)~%{LiEks0N#jEfNNWiqDV!O*Gmc0SfOVlmXIgTKVtN&8yUJaw7mQ^ZMq9 zeXW2;4wwcxv-QP>5)vr#cSe^n^0n{YEqb3ldv+#V{e%h|{Sff$PjEz^*UB+~i2};@ w5(DtGz?Q7Bl1p92!r<1d@hG5X!U65Ag<(T+6EP9^4KIlHWnIl;b*qs70|Xj_82|tP diff --git a/files/opencs/raster/startup/small/configure.png b/files/opencs/raster/startup/small/configure.png deleted file mode 100644 index e91b7f773150e81f0276b058be4a90fafc54419e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1450 zcmV;b1y%ZqP)FK~z|Uy_IiF6L%QLzvZq4dUuVvU|q`uvgu5aKjIrh!V43V zfg$KtdabC?fF^BbmSC0u$&&5GVzzfvw*-dU8cjMgXdr?NWr~>0#Ka06p+LoAxvL5X zY@MY?DJ{1b6EjqtNZBvBH<#ymKA+$7d-D4!WLd^WEUVR8q);eq48!cEX*z?ZX&=k7 z)z#J2{r`?56AiZ6Y};j7{)%BVhOL>IpePFSQW)uaeY?S6=zl(5k;o3%Y_^h=l$4I0 zIl0QLceXFX7!a^_%1DdAs z2!V`3We+mIRcht3$nAbX|-AnlKiJ6K@gB6sT{y}&&T)m^?m(#a`PlC6?jOAO?$xR(8j>XO3F$y;YN}^y zY6{V46rw0XtJNYmHy3KPdUI1#lSon2XR%nUOQljZ&&|!1=+ZO5^L|K@)SnOmk|cey zEHA|4@#NXrSpa}mtHri$+dz`!=Brn)Hpb&|P!t7S`c|Z>DLi@@z{0{pQ$hqh9?!=` zMMcRzpAUMy9#d0O&m^+4vT*e1Q3QiQghC;NLLqoYMlmglZRgIN`)%ClQ9ux+KP8v4c%bO=G}1ZbFmEbgZGFp-~V71wjzl zwQCm`hQa;&_i_97ZIC1xB}r1H*X!Z)`7k~{9+4!eOQ}?T)Y#Z4KI_Vxyai^n*-}?m z_oE;PNy~7pR*Q*=2{@fjxZUnrj^n=ddcE(3!{NO7`T2)SOG|goo;|zpLNQsE*Yp{U z#v?A5E8gDTE_ZZv$UM)>kw`@LdcE?gQ>T_pCew+v)LRpr<2bX+<%+EaHyVxBwQlIO z1FNd4%r2K}aTR!+F zD+EeQOG}E2i=C5`lPOB25;-|JU|AMC&!elWYr*UFn(cObTO#4W)5mDvzJ2ePl$5k7 zmC6_3Jv}`Oe!ri)aN$B{qTv8Q^!4=>?%lihbaHaCU8B)V>vXzzbvhjx2n5jG-5nVp zALkqn$K{QH0|1$ypZ}#sqp3f6@?;MH+`M_S^~Q}G#*vYch$xEXPN(yije-L}QZyRf zt=H>)%Rk1(#s=@+y*tnI{0AEi2LN)%jvaSGq0p7<*RK~pc<>;jx3~9zD2fNKUAy+{ zM#BMsOixe$WOQ`&``+H(p_!SPVNn!6b-UgF3mgFc25+Q-YLBZS&;S4c07*qoM6N<$ Ef(F#BDF6Tf diff --git a/files/opencs/raster/startup/small/create-addon.png b/files/opencs/raster/startup/small/create-addon.png deleted file mode 100644 index 64fd138be55a6d2c73bff277da82b8f72996d707..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1714 zcmV;j22J^iP)EkL}n=Q>P_GElP_57?2uh zl~B3>RuyU&6c*_g39$eHLI}YEu|!A+35gBjF8~&)*fj;Rp)_AjMBETJwqwVW@r=iF z=YE`zmxbfe%y?2aEfQBcI``cBp7T8CdEWQxTqPp>KdX8h!rdFFpIx%);!qvbFV~I8i-(d{*Cn`ic3@ zq4~!Si|5%bHMc;}JY{0ubmmR+!L=^=|LS-}%iO zzdTTZ)6bv!X}4#7zVWYRk|ZHbH<db%p0XB|qQT+}pD&(u`?E5KL+dTwH?D|C*$~2N4UCTu}jz4SiOa9w&`p%$#!BMuZ2v768`G7 z=RS5I0cT8gG(fVPiDJZ&3C-O`7i%qVUb(||v&TEP+g!ZaK#ZZYc9TC|cn?8QE*Gg! zl__gjZs%l1Xyx5cA4ovNw?_k-siSK>_flcL6p$Ik&CMN5A>?ky(2jd7uWd0mU7~q! zo3*WNiZhcq<7vbe=N(B9aHv+D97kZN3Ib@32AJFudJjQFyHe%))pyx$Cqzo&t>eX~ z=DBdAK_ybG#KNEcdYwX8qBapSs~n|Bk!j6N&L<8e0RGNsKqqmO0x%fvF5lsm*M7t| zzFk2f%-4#v;*3|UCki!jFT;AFHd&xj3PA+x9B~};)?a@6@S9syo@6BDhbm?!cR!h#DIrIEj0&l!^V`*`5aUX$3Gv(vQW|)gS-Z`wbbh};Z z^*YCn9UHP^)Pg-_9y$7`0=}q}8cOsb0wN-x`|RS-E<^-tEuu9qf9=$6Vp)r|7VkaI zIr2Owj$^Vc8v@-U0uTfNg+c-Ey*!Y>E(t#*CBR1Pd3Np*w(hNixIVD=9&0U~PKS++ z4a(*6zPWp2r4&&V5d=Z~^5x5a7ZGW-TC)E<;GKww8dM-oGMX#@LF3ViA(^eUBuT=| z%uL^o(W)4!j?jDmh5MwM`v@rI#e1&?<%<(TDjHOP7r{B#Uvy6it@Y6JaV0tC-F^kU zAcGQ$2<=`@H#JOFCSYLU2el6fj);uO0cT~@$2@l^rO~BmXxF2ld(RI79qjj8AYw!$ z9L#}kYRIi2-fZEW?@K)hx<~9`EO5^4C*YCXdtV+T(90~Dm5A=0Os_AEB{1fRekTWeDI(C7kuV{)b13k1iYr?}?&-Qn}2@Pd&}yxj95M({u9#QHgGxpf)%ANja_(9=ZyY zR~J9CxX9VFX9MP80Gp z!Q`o!EXlJh&7N3%vEp1+f6ywp-(=sG+Q&Z^0g-y>Q|J~ z>psgGi4=E!_ve51_U4>BcW!lRYN|L|W}~3q`@Ou9cDtSY7kDUT2S0d{H)qMod4BKn{Lk|op_JnP*^K}703n1>z(aR$s7WN7>%E{;FI_h_ zru?Snx_GR(??C5$Pwf6DpYf++>iXKtS8Yf&r#IP|emxk8H!etPJ!dU!DAqO1ZB()n zZ9mcxP1Ym=sk+8m&-Y`FTZo!wIILl41tbbdEjlQu4D}Mx1pTGTwr}lx{oxrs(01wS zACxNY_JI$(s8*|3)&S}An^uqJOiDxjR2+#0RBfGHzK8v9ALXr{0vp$~qLd_MNJ>Jd z;`s;#;h@HlC8?>Yy%IPrfHrwz>U!JM4HerXhD*>iNrZHaU=T;sIo^|_KVPLhG{)i1 z!INNmkboJ$)lAG#ka#RWbKJysy}6$iLhaO)lgQ2-TS)v z{Ja{jxoRmLeFgsASH!44gW9??2-L=jrsLQ~h_0fCYrD+|A!Y{ftp%x63gw)bSjtyj zvY8x0D4LTxePbTEe3{} zjhD=y5#W=q%)C8szR7i86kKvn2moa}M1m$w5smCn3D5I* z@!K1(Ud>H6-@>-7_wmSG4K&qOFr);blaJMMB$K0%uTqtQvgdQb!Ui(g5@i>9%Zk6g zbcAE=1N0Xx;?g5!3WTOH=JUB30ZLW9`lwJKVD7997Eue?K;81 z50Bw_KALHA`=#?~KT$+VMY<+J%oOxh1s`;c;QAg#ElAeYsCp9Da+k~qfbu)~hirB{ z2lrlmf@EB<>Hd%S+V$t+Iz>AAihMlkkTwNN8^S#M%E$B#m)N+bg#~p{E^Tc@pg6Fv zlaD%vNz?>MBx9t4ig+kAGl1{O!vKT%09&6*^U$U&ptJdVySVYTHa_|!gB}P_4##P` zu!ZHz<}#eGGLRV|RTpR0>@-2;G3vtZ!&&l!1qwq2#zu?et6uZz0H)Leb!dFlGZf_e zPlkEq))DT1vWYvlKF8mly_E~D{T)FBFTT@`U9y-J75w3?9t^#gXvm-{LFoosB95gQ zc%F?FG(f53j2?LXgQFdj=SO-1Y=27fi*FP-HW1|ghn`{Audk$#D{!#4!0}Os6JkvLRG5Pr?uc@zxA3ZUux62 z;gK-sFUfK9HLYy8doTH-!<;$wJp0GLqiY%{i2{^Tn7Tj{pcE(p(wFFFFt2I4b8?Gf zU|_)0rV=S7ky4_Rny8@fb|g5SspIkQIP5u6%dYnd?A$Vou&x*$sgO>k5JKR3KDO;J zI+{Z_L*$ATa>X)L%OhVbBc-ZsZ*Ont>+5SA92{({tE&r5e-B7sDW$|XfKrNb)u!io z5@*b2$DNA1ehJ&}n8(9+jquyo<`4`9h(sbZHa6lo4yI`$gurneB9RD6DH<9Y@H~%2 zixy#;CWc{9DwU=`NQ96|N-4&@08p;FSW2U7a1k!;Y}+V#@K=58xMwl9t?5N6IC}Ib zH8nLfH8rt+|9+CmB&KO%nkK`;!w4a0ZEYnUkE4`AN=czmm<}M-$@q*LS4xquIFu}x zXe>g`IhT}pj~mu`-1BqA&TpD@?R$)c3pWvu#|Z=iELpMyP1A53heRR)fUfKKzCU4n z+{QEj<$G$X1{}vn2n{V{A{~c=nZ=wn{}AiXhr1t(@|T~y%<+Rd(OK(oT^GYJs8lK# zhCw!)B@hT;+ct?rV#2tT65sbv69T1NrIayQ10~DF@m$J-IUEffU(w##LhIbaRF@fC z`#_QBfBGWFifQWSu0_){;_*0|rePQcwrvv*hjAPSP18<+m>vR@Bc%*a2B_E`aa~|( z21Qqr2q-G9!3T%uv1)l2sm6=>>ehb#^zhTX{Kf#G)!h_Iv$^3%dH(#9J!C>{ z)HEzdO3BE`$b>h>jZX4_nyvw%#*|VGlOcSmsFrOaI^=DifUY4Wn5ITNnxtmgHmDzZ0s*SkDwkb$8M>~YBE;z)fYM~j@mBI6AwUxXAtavb zV+uh)7sOI$vSiH@d|lki)4Lj}n{yHU-6JetaWV0DeBvzM_a|<`XKDbUvQw`ifFBCs zdlJX-aMVc<*D%mDf#-YddG`dCw}}nwx)|<$iI!QH^TzJiK zC6P$v^tV8He(%(4sFY%;tDm}gX$+;%O%vO3a4khNY#@Z-iYu-_)7FvC=h*$iqbxJ_ z5|;D1V9o6SG&eV6+ct(_oT7#40Mb3GYZ|kg8$n7;QzsM-v;6F{XsoYCDUD=(1G*VP z*L93w1k18{<&{^dudgSc&l7EIKytDfsyzuHVzOeSoB$G)D!yp!mF?;sx|B?r! z*njSd6>Q$T`J@5EnCMUUxvonf5TK={h34jFT-PO^&$DdVGE()6+3~Z7xqkEQ)YqS^ z1%W_-!NEadvDnF^H5DM?8M~!x$#gocjb|{+vcR_pYT%VE%2pM}s^U79s#MBu>#C~) zy}i8*4-XTKM#*F{#N%<&^|Sc)y$|yK-o30^eGy}0W0cEfqR}Xwot?yDF){5u0C36j z=C(@Jc|a3F>AF_ZHAYj3@ZlxRwFiXI`lW3TRaJ28H_yLc*|KFz)B5%64;hA`CaZf~ l^-8H}sr}Qj%H?v_e*lD3ISjB@*9HIp002ovPDHLkV1n`IvH1W1 diff --git a/files/opencs/raster/startup/small/new-game.png b/files/opencs/raster/startup/small/new-game.png deleted file mode 100644 index 0d7d14c5580ef04b7b9fb4c31cc29bddfc42f683..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2122 zcmV-Q2(|Z#P)6z)7^~}yb?2R{e6en?F=Rr_n zDUp@H0XRSoIVBvzi4%ew5{eLEbpj!T@WA)J`BJm7XQ5-G zqT`II@Az_VVLGV~-amci+dnz+2k!EwV)FjyUU>GU)$quw!C`y;LKop zX8){rOjj%O6J7vqVUYLF@2;x|xWgmyfF@*vTZ3hfDR~Z)$38cAVyLXjSCol5$)jqGh_z2!R zTG}xbim@#b9^y!{nK_!xsV@L`1dulm0;P^m3dXsmjv)#H8Wn|(A_}Rve07`EUdm{5 zhjUjpJ^@n^VGv?uKv0QD>LCkBKyK{*yDg!-btDIqf>BnI%rqHV!AMk?t4Vw|=EVCS z^VnjOm%sQZm#+2s`?UeOvzMvqy@XQn$#+I9HZ)D`^&4M%?*3f? zN@J2qfzJ!7QNUbPvc5UQTFYCfuhZ|2IeWFosmq&qV;HPl=4U5A#v_R1h^b~wEMciv zkQ>i-F??cI0PkIIQqavxhSqT-^UPEfxe;96*ujLF^?{)`8nd*rMQ2Zi?u|YxTYaKd zjnX(aN0!o+q)IW}NNRTixTOjdbSDL-uxRH%1@!6(=g)jdzn2mSfwLu_KQzP1i<{H~ z!ScxSn?If>)D;?4OHxUwlY&87Qc)lX%zGbVZHWv5zW)6E zT(~~KIZwM;r5*^brGj^tx|q^3kP+)fl3It!%!9iF`0~=EAT2DJhdi}hx;Eg0t%7zS zcyuP_mw*0%Yd40x^5P*5OxJk+ky!+u)9+v5AD3>>Xht;l)M-VYq*B=(ptSDXq`)~( zS~ygodF#w7DhwIL3CEu~#L*-BxzS5mTj|o8PMF`9JBf-s$Ji2IuM^0SB=Y?JuNTON8S^#4&)>d^R?E~Xn$&?;8rew5q{iBu zEYjfpo?RVy^WBTLEdju0HzSEcI)THDN35^+IkVj7@>aq15uh}smjvF?juk>F7TY1c z{+O!p7$;Cs6ukcGQ@8Di6J_YVz~lX`&033>l9#`9jDww+y@{q;39!yFGgBi-s-Pr= zEdhuFIMS(+Xs|&@b-IBoJ*)lf@p}S%0^xAZeHt8KZRBWm=80{OxoSXJl#GWtk@ReA zjtKLVipl92LDzuBGvA8IgvM&g&PESW6Er z4P+2;_|XHjrecytg`Dw@#czD@nDYun2zr&N9Yp?%&%&g#ki<6OwdP zkc~5(6u0e$x931<{bUXlMTrm+SqX3^M=Q+>kIi#&BjYcpmZ?S|VXeZBbsSu5k+ka+ zBESRz9!2Rv2Aa(H#mRWL{SylBjrU&PoCCwmP*}rgW1B*P^`8B6O%B{QW@~$gPAeg< z#>ArJfmVg?MjxD`=o?BQDML-I7LX6i*6vGyFK%|gd(SwxBue14W>88Rp~OkaGY6a0 zv_LqE9}N*oQH?{&!lKHOpb}6C1hKR9w#GP!zV+o}v-bp$UgS3i2gnP{w((T7Vk!yQ zN=r=XNaBcx7N-d%*i!K1107-|2ttho3NINMM~OrV&=PED_TLl03%@g2hNbhQqntd; zSnCc6B;X+kB>QF((lnn;7z6@OvadrZ1x^Tbs7abtv=pe)KX7jcU^rQZ07DB# zfD{5D9M)L8aYRyZU#rf^iyMTIrWyx`W|Pu+GHVH<3h~S|#tE|0*{QP{{9RL6T07*qoM6N<$f_W47 ATL1t6 diff --git a/files/opencs/record-edit.svg b/files/opencs/record-edit.svg index 2db10e9c78..5e7a058782 100644 --- a/files/opencs/record-edit.svg +++ b/files/opencs/record-edit.svg @@ -116,7 +116,7 @@ gradientTransform="matrix(0.2267857,0,0,0.24804718,6.7279833,5.1097051)" /> + + id="rect2091" + style="font-variation-settings:normal;display:inline;vector-effect:none;fill:url(#linearGradient2178);fill-opacity:1;stroke:none;stroke-width:0.89642px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:fill markers stroke;stop-color:#000000" + d="M 2,1 V 16.000002 H 13.999997 V 1 Z M 2.8571402,1.9374994 H 13.142856 V 15.062503 H 2.8571402 Z" + transform="matrix(0.26458331,0,0,0.26458402,33.866672,49.74166)" /> + cx="37.306255" + cy="51.329163" + rx="0.79374993" + ry="0.79375207" /> diff --git a/files/opencs/resources.qrc b/files/opencs/resources.qrc index 786040623c..3b5a17c730 100644 --- a/files/opencs/resources.qrc +++ b/files/opencs/resources.qrc @@ -102,7 +102,7 @@ raster/startup/big/create-addon.png raster/startup/big/new-game.png raster/startup/big/edit-content.png - raster/startup/small/configure.png + configure.svg lighting-moon.svg diff --git a/files/opencs/run-log.svg b/files/opencs/run-log.svg index 404f74a6d4..96e0d037b7 100644 --- a/files/opencs/run-log.svg +++ b/files/opencs/run-log.svg @@ -67,14 +67,13 @@ gradientTransform="matrix(0.26458338,0,0,0.26459185,23.018732,7.1420691)" /> + d="m 57.904031,57.222619 -1.429163,0.842578 a 0.07915963,0.08113535 0 0 1 -0.118629,-0.07046 l 0.0027,-1.689872 a 0.07915963,0.08113535 0 0 1 0.11885,-0.07007 l 1.426507,0.847294 a 0.07915963,0.08113535 0 0 1 -2.21e-4,0.140531 z" /> *|a)pj_d96d3%yXVb; zAf;lAKmn+v^4HH==Kz@rDL^u9m!n67U9j_9_jT=a$=82|MX&hbAKw;#d%T;?m&Jcg?r-|vO^Y%S|B?H=C|oH-@zwo!GAgVz zCcG+&e*ev9pS@muxcRIo3Jh?%xR@?3&)zSWcNgd9(?vO8f8}s?d;Y`VL-oyU{?F>_ z9D_eUt80vgn;Lib^GP|I-<*$z=hdXTt)|Nbipys4VO@MUuLjHUhYIcA&d?icP8Xjy z3eQK^o6-*t59NbSg=CEJT&Q!U`Y6#~e4H)^kNtL4%&M!Z5<;BgSzYqDQWuYt@${D` z2a}#QdxvE2@ZYAwnourg_w!+Ojha}Q!M5987DJqoChe!j29H}99}H#9}aO#Lvf z9=@7AUY?0U6jH_#vxU+@=Ui@OhMuk`vxm#G597u7YLdIJrUQKKUk!%8+{|b9Q*=J9 z9*UE|X{&dz_rL>A|x z!H4na{B5=PWjVV$uSakqQ0q5)B&rVg>Y|&x-RG^&vX?kllUQ)W>;yc5hzu%V4s@ zhc(a9^Q0Q0)xqRp@NuzeI@$ci`*{T)^?3)!nhb4sp|y6K$2TkDPt)-d&gdRW^8-!c z?^Ec{PN^T~gXsd^30Xlr{AlWi`rLt-Y?Pon`0EX35$oH0oKL{GadNefRcfDStkDS|ekVzrUG*rBqf} z;mJg0tkF7uGeO9nTzyl$KKW`r{9~^V)AJ$(uWKeh%F8o@(jmaRZKxkX4QNeBC+Z?IwOJ{6N`v{tIrM2Fp(g zx_QD%ySdRJ)+N7A2FvP;zAl9VLxZgPs)rxeS%L57)gIllFr8=W7DexUX${<%Y>6HU z5nOOCv_u~Q6e4KvTEf?|)Iuv|cVyWmmk!2j#m^r3u9=J}h7MV7x+%T&8d@Co_jgdx zb$?zwN2qOX9N=6xmpdhLoHRM=l@oG5@ADr=DcUxZ*F1^p`OJO7Sa&74?nDar0uhz~ ze-XaTbaq!O$1^1Qq7V@Fg@6>!PZa_%DdCN?uGg=>rf=Y2XngC-Pe5sbE`gwvMhPr}r_8J-lXfm1mT>yRNxwUwsA zUAI?TyvoJ4&_S!*ei{AV)3irS69M_CX|~G!uG2hVBC4?j|Nka(D&0}fP1;YSVZUbz z$Z4JqWdB>_fO}Ub$j?7w74lEIJh6A#jJ*6K)+dhP`nG)nwCEbAWP=(D$y zPK32iJk7oPDxBsg86rN#jg^C(JmE198kZH`rsE#z7f)&Rz!jK6^cn!pN)JTkKYhL( zG_>csu76Qd)1}Y=!pa0{@%mf8i)d`Aq!R?sq7*>~sbBrxY&5$odcVz(53l=4>EwFh z4*}p$;PelH5_39$_mmCybqQQ`%MS z z{nnIt;Xql@K8k65>6C39+a&&oW$;GXWARhkh~mNeH?KFx@OcSf#Oo8EwdemZo{nY@ z{q=PV7uU?(F0s0w5jGpjn*w)(n`-fXFygHX)%9?-8<)0MSI=+m$D?XFn^e#u-m{R6 zM|1Sl`DAwWzYtaTv#Z&BRLwU%$*l)z`wo(H>2LLjV_QGSdzI_aL#S7SjndawQ|=yD zq@C+2n5ELz~EFAPYL3&K6$(S2(>p0s&$+9^Ep(d zKN)m?-;RIfHIBT|d0Q<9qrq}eGi*)KXK!Bf z&e8Ql{&zASRtr{H|M(%@Ir@tCa29KYhN8pD%UD|u3FN$jvXAu;=cDV3|9<<;o7cnP#Xo2BU+UU?GPs)EqfcI28D7xa z4VG`lx6m8j$NJ0T?F5FldB8Huk9SqwL~dkW z@NF~1^EHpV-CE=1#^%1lF;2bfu0YpKf!%uo?OOuv9y^aF2gU)*ML7*8EsY?uYwyL^ z`~C*tLNreUb)7K~{5-kqzuBI*JA3|hKUO#ujE(#H`C9X{+pgc&t`j0Ct@gDWuJ}1T z{mXCdF{Z9VOsykKPpaGc)K$KV zgnuO3vBDVB1Fr!5wU|*iL1;64{P4 zww=Tt;dz-;OGnR}a_YF(^NQHOupKTgyjNHrIy?w5p zZ!dQ3E_HP*bTyZ`j?nR=FFqaRX9g8NhZ(pJ0tmNX?XDC*hx^zK|8*ZSAaaJF_w_T1 z{2c7Z?=vGU8+V*uZbJTPKMiZvRvl98Ys1gse)d5=h-I&_zBT;pwz*H%-s!zEcp`53 zeXF0%U_{w4;hmml2SzPwd^;g@w zLpyszT{}aKeW63RsJZO8UMiE#butx{*>`IFC?A$$&fp+Zf#&ua3mms>+;V-n@k{wa z?Fi|wqrI1@LwGh=Zth!m>{{>cS+{nqPYlpr;AW)S1!G5XK5xo&wD$yXxEn0rf7Wd5 zdOVq2+|MUpeBQB)zh3=CtKS#E2!M0O%AQ7cfrKr{;_JfO(ikm*>uEYFE^qW;Dn>fq@bcKq~m>*phBu_f{)|}sjvmJqN2%EQYt8qThW}?iDtcVGSk3p z4SocS7pa%-8DD|{QF=5PxxWE!$euB!@L)qIg5xaHhL6J36$V)Z!$krh-JXG4h)#lS z?+PF3qO{RH%^XEYM;80QI|0f;Yz7mT4pfgmy$#Y)L@prOyH271T72J}yuhF&=ETVd z@Wy~Wpkjc=g_vmwzP87#)WslUi**>Z^3JlhPVdi167nVUK3xP12@KO_Me|w~)HoTC zH?(TgmZQw_Wk1NBNs}T9XV>GwEE}~OBWFJf&{U;6b5Lic^LXGStBn;x-`v; zt8zIJd6u!6ma$1*G~>-&^h9A$=HQUQ;JuTf7b5~-v@o`axke1#tY}^vXfX6zn`A(B zZO3;U1VNPpHA>?ZXnc1IlqT7bG>~3FU+1hSU0#Cdx#z zurAx=83!g1eyA73#wu`z=tg^(R3b^5DAIPPgz02=mte!TV6(6|J&mC_wx%RKTViMA zRYA=&28jwLMi@~rlC}u*B{5?7@I@L{#}zi4XviC!K%D6*R9IJr&3njI$ew~#I+S#@ zc>{*Q=2g1H#*%O%fu}q;o}g?!9s>*sHo%iPMdyI=z(N#w;?f&yoP-)_dJN$_++{B< zVW1mHmky@~KOLCStZD;bISq#oMS+uXo;^9VqhXPrj>RiQ8Y;sEF*WW^Lkm{Zv8BQ| z;h3!FbVad zT-`dAIKoQht1bnA=X4g%nwnP%5h zN>qGormlEamxZ$EEN*mTgn{$x0TnxyI03gEqVd7^n8i!I;1ZABun1^9I>P8THY1RNgF}hxR?Y&0@o)=)a5mXyGzp}@GGTgVrH`17 z@sbz{_>3(gZU$be{bd&2M6dB68c$*I-(>ntD?auk<~1@ zYe9F7w$}#JMU^RT86pLrFcijbq&8~5b#vUXw(W9q{Vvr)x8R3vc2#W4Rp8y@M{f=7%-g6}V( zxA6)#4E~ssS6jWxRK9A?fd(#HsC|m{P<|0XqY>DId2FfI?g)X+0PB(pwLr{>iKbk7 zfr8}fCE#zfHm%i~gfcwLEHDi9s0)GTt_r^R4KUQLNEjsn9JE$59yKb18o9EToAfFG znBfaVBqV-<9HAP z97e>{4Z#C}Lem){lpvj0XL=GSH3m9U$y8FNHjsJ3wYkhqh)?PD5acS+TMdDU9uiHv zPV_hgv;)tOMnD=Mh|ZW6PRQn54YE?JOfM=pl1zo-sg%`0L`e-mGcMQ0aH7v?4J59Z z=@Gv=LY>4EBlDD?Wd=&g1d8V+1k>ErC_^TtL3VL;S03tkT%+AKt~pSL=csnQdM3>8 zCl#!I`fD|xeObRL%(e^|z7ASx3m17$-KMnw085~F$=8Nu)%2L6UXcI+C20XBszahd zZ;c@>7Xfxn6}bYyRR@@3LESz zOIe`h3o{cf@`PMYJ~(R0aG#726u4aGhzS88@gWWVG&0r6G@EI;TAKa40@#By&(5QTDi$I zB@Mh%mRhqe1d58nx^*Y;Hlzi3NWUJ7h;`k@n0@kv%p~*NkbTC|(79T@VT|75(~jw= z`Zmt8V{Mq{1-(_Pm#MCQaeoWQ4DOlxTcR^jAn#EeQ8>bTa40b|%S#PUODcrx_{w8M zt^kLndI@90caucV4A(>iqmUcqx(*UaOD=+-Kc?8Bp)N9~|8)P!Ga+W>We+HY5JKm? z9Qml^$`nE&5&^Iil7RaUG3V+sA~P!#(WcDHZ$KPqiQY;<62K-V=lVe5P!&f9sd#GK zalsX#k9ZPsnNZf!j-fy)m4PKhCN8AH|4GOes_cQ$nCihBc!WnonhL-uDS6OPX2aUf z5&uKQ(t&3<3a2`m+eOsewx`guXq)bsbBZhcu-(;5}G{Xq;S{y{Dn#mas!vd^HS=BgT)-!vmJ$GB0yR4=r22$d0uKgo3#FG9 zl_D4Q0+W1|k{H3hb($hjKG&vzu9Qr)R2kyrL6rd2&Lw?bUNU^=K;XR5AhQ_9Q*%8j zR(-E?a;?)l2{wad5(p0^nW(KNQ`7u<+N-2oZ>V9#j7?c0_!To%nj(@!z4ItWadSArzc4}$pL#I?Xv0&cjzS<{eEB&~+OYL<-YrAkzm;0-D1@D|{t urLwGOISqzp=*W^7YO2e1=MaGOYE|n}O4v}^Us!y&dGmj~yKb?uU;qFVzEZ6K diff --git a/files/opencs/sound.svg b/files/opencs/sound.svg index f482c9cd70..ead929c862 100644 --- a/files/opencs/sound.svg +++ b/files/opencs/sound.svg @@ -120,7 +120,8 @@ x1="17.991667" y1="30.691668" x2="21.960417" - y2="30.691668" /> + y2="30.691668" + gradientTransform="matrix(1.5999994,0,0,1,-10.794989,8.2675378e-8)" /> + gradientTransform="matrix(1.5,0,0,1.6,6.0324979,-18.415001)" /> + style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:url(#linearGradient3131);fill-opacity:1;stroke:none;stroke-width:0.334674px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000;stop-opacity:1" + d="m 18.838333,28.575001 v 4.233334 h 0.423332 v -4.233334 z m 0.846662,0.846667 v 2.540001 h 0.423336 v -2.540001 z m 1.693335,0 v 2.540001 h 0.423334 v -2.540001 z m -3.386665,0.423334 v 1.693333 h 0.423333 v -1.693333 z m 2.539997,0 v 1.693333 h 0.423335 v -1.693333 z m 1.693334,0 v 1.693333 h 0.423336 v -1.693333 z" /> + style="font-variation-settings:normal;display:inline;vector-effect:none;fill:url(#linearGradient2264);fill-opacity:1;stroke:none;stroke-width:0.40989px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;stop-color:#000000" + d="m 17.144998,27.305001 v 6.773334 h 6.35 v -6.773334 z m 0.396874,0.423334 h 5.55625 v 5.926666 h -5.55625 z" /> From 9d04c1b2b8ae2fee1e07902dba1b50189a703f8c Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 21 Apr 2024 23:34:23 +0200 Subject: [PATCH 446/451] Consider absent value in a frame as none There are frames with missing values because of loading screens. They should not be replaced with zeroes or other values. Otherwise showed graphs and calculated statistics are wrong. --- scripts/osg_stats.py | 76 +++++++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 20fae2cac8..81739c1b57 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -159,13 +159,7 @@ def collect_per_frame(sources, keys, begin_frame, end_frame, frame_number_name): if key in frame: result[name][key][index] = frame[key] for key in keys: - prev = 0.0 values = result[name][key][:max_index + 1] - for i in range(len(values)): - if values[i] is not None: - prev = values[i] - else: - values[i] = prev result[name][key] = numpy.array(values) return result, begin_frame, end_frame @@ -187,7 +181,7 @@ def draw_timeseries(sources, keys, add_sum, begin_frame, end_frame): y = frames[key] ax.plot(x[:len(y)], y, label=f'{key}:{name}') if add_sum: - y = numpy.sum(list(frames[k] for k in keys), axis=0) + y = sum_arrays_with_none([frames[k] for k in keys]) ax.plot(x[:len(y)], y, label=f'sum:{name}', linestyle='--') ax.grid(True) ax.legend() @@ -199,10 +193,10 @@ def draw_cumulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): x = numpy.array(range(begin_frame, end_frame)) for name, frames in sources.items(): for key in keys: - y = numpy.cumsum(frames[key]) + y = cumsum_with_none(frames[key]) ax.plot(x[:len(y)], y, label=f'{key}:{name}') if add_sum: - y = numpy.cumsum(numpy.sum(list(frames[k] for k in keys), axis=0)) + y = sum_arrays_with_none([cumsum_with_none(frames[k]) for k in keys]) ax.plot(x[:len(y)], y, label=f'sum:{name}', linestyle='--') ax.grid(True) ax.legend() @@ -214,10 +208,10 @@ def draw_timeseries_delta(sources, keys, add_sum, begin_frame, end_frame): x = numpy.array(range(begin_frame + 1, end_frame)) for name, frames in sources.items(): for key in keys: - y = numpy.diff(frames[key]) - ax.plot(x[:len(y)], numpy.diff(frames[key]), label=f'{key}:{name}') + y = diff_with_none(frames[key]) + ax.plot(x[:len(y)], y, label=f'{key}:{name}') if add_sum: - y = numpy.diff(numpy.sum(list(frames[k] for k in keys), axis=0)) + y = sum_arrays_with_none([diff_with_none(frames[k]) for k in keys]) ax.plot(x[:len(y)], y, label=f'sum:{name}', linestyle='--') ax.grid(True) ax.legend() @@ -367,13 +361,13 @@ def make_stats(source, key, values, precision): source=source, key=key, number=len(values), - min=fixed_float(min(values), precision), - max=fixed_float(max(values), precision), - sum=fixed_float(sum(values), precision), - mean=fixed_float(statistics.mean(values), precision), - median=fixed_float(statistics.median(values), precision), - stdev=fixed_float(statistics.stdev(float(v) for v in values), precision), - q95=fixed_float(numpy.quantile(values, 0.95), precision), + min=fixed_float(min(values), precision) if values else '-', + max=fixed_float(max(values), precision) if values else '-', + sum=fixed_float(sum(values), precision) if values else '-', + mean=fixed_float(statistics.mean(values), precision) if values else '-', + median=fixed_float(statistics.median(values), precision) if values else '-', + stdev=fixed_float(statistics.stdev(float(v) for v in values), precision) if values else '-', + q95=fixed_float(numpy.quantile(values, 0.95), precision) if values else '-', ) @@ -384,5 +378,49 @@ def to_number(value): return float(value) +def cumsum_with_none(values): + cumsum = None + result = list() + for v in values: + if v is None: + result.append(None) + elif cumsum is None: + cumsum = v + result.append(cumsum) + else: + cumsum += v + result.append(cumsum) + return numpy.array(result) + + +def diff_with_none(values): + if len(values) < 2: + return numpy.array([]) + prev = values[0] + result = list() + for v in values[1:]: + if prev is None: + result.append(v) + prev = v + elif v is None: + result.append(v) + else: + result.append(v - prev) + prev = v + return numpy.array(result) + + +def sum_arrays_with_none(arrays): + size = max(len(v) for v in arrays) + result = list() + for i in range(size): + not_none_values = [v[i] for v in arrays if v[i] is not None] + if not_none_values: + result.append(sum(not_none_values)) + else: + result.append(None) + return numpy.array(result) + + if __name__ == '__main__': main() From d10b0d503b7149a4fb1d1c632f292655d3a0488b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 22 Apr 2024 11:26:09 +0400 Subject: [PATCH 447/451] Do not take in account UI scaling factor twice --- apps/openmw/mwgui/windowbase.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index eff5c98588..f5d90590f8 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -7,7 +7,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include #include #include "draganddrop.hpp" @@ -80,12 +79,12 @@ void WindowBase::center() void WindowBase::clampWindowCoordinates(MyGUI::Window* window) { - auto minSize = window->getMinSize(); - minSize.height = static_cast(minSize.height * Settings::gui().mScalingFactor); - minSize.width = static_cast(minSize.width * Settings::gui().mScalingFactor); + MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + if (window->getLayer()) + viewSize = window->getLayer()->getSize(); // Window's minimum size is larger than the screen size, can not clamp coordinates - MyGUI::IntSize viewSize = MyGUI::RenderManager::getInstance().getViewSize(); + auto minSize = window->getMinSize(); if (minSize.width > viewSize.width || minSize.height > viewSize.height) return; From 6390fdee7a86d509554fd83bd8a71bc56706b7ca Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 22 Apr 2024 20:41:58 +0400 Subject: [PATCH 448/451] Remove dead code --- components/CMakeLists.txt | 2 +- .../contentselector/model/naturalsort.cpp | 112 ------------------ .../contentselector/model/naturalsort.hpp | 11 -- 3 files changed, 1 insertion(+), 124 deletions(-) delete mode 100644 components/contentselector/model/naturalsort.cpp delete mode 100644 components/contentselector/model/naturalsort.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index df23121c5c..e17449aa24 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -515,7 +515,7 @@ set (ESM_UI ${CMAKE_CURRENT_SOURCE_DIR}/contentselector/contentselector.ui if (USE_QT) add_component_qt_dir (contentselector model/modelitem model/esmfile - model/naturalsort model/contentmodel + model/contentmodel model/loadordererror view/combobox view/contentselector ) diff --git a/components/contentselector/model/naturalsort.cpp b/components/contentselector/model/naturalsort.cpp deleted file mode 100644 index b090137d97..0000000000 --- a/components/contentselector/model/naturalsort.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * This file contains code found in the QtGui module of the Qt Toolkit. - * See Qt's qfilesystemmodel source files for more information - */ - -#include "naturalsort.hpp" - -static inline QChar getNextChar(const QString& s, int location) -{ - return (location < s.length()) ? s.at(location) : QChar(); -} - -/*! - * Natural number sort, skips spaces. - * - * Examples: - * 1, 2, 10, 55, 100 - * 01.jpg, 2.jpg, 10.jpg - * - * Note on the algorithm: - * Only as many characters as necessary are looked at and at most they all - * are looked at once. - * - * Slower then QString::compare() (of course) - */ -int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs) -{ - for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) - { - // skip spaces, tabs and 0's - QChar c1 = getNextChar(s1, l1); - while (c1.isSpace()) - c1 = getNextChar(s1, ++l1); - QChar c2 = getNextChar(s2, l2); - while (c2.isSpace()) - c2 = getNextChar(s2, ++l2); - - if (c1.isDigit() && c2.isDigit()) - { - while (c1.digitValue() == 0) - c1 = getNextChar(s1, ++l1); - while (c2.digitValue() == 0) - c2 = getNextChar(s2, ++l2); - - int lookAheadLocation1 = l1; - int lookAheadLocation2 = l2; - int currentReturnValue = 0; - // find the last digit, setting currentReturnValue as we go if it isn't equal - for (QChar lookAhead1 = c1, lookAhead2 = c2; - (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); - lookAhead1 = getNextChar(s1, ++lookAheadLocation1), lookAhead2 = getNextChar(s2, ++lookAheadLocation2)) - { - bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit(); - bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit(); - if (!is1ADigit && !is2ADigit) - break; - if (!is1ADigit) - return -1; - if (!is2ADigit) - return 1; - if (currentReturnValue == 0) - { - if (lookAhead1 < lookAhead2) - { - currentReturnValue = -1; - } - else if (lookAhead1 > lookAhead2) - { - currentReturnValue = 1; - } - } - } - if (currentReturnValue != 0) - return currentReturnValue; - } - - if (cs == Qt::CaseInsensitive) - { - if (!c1.isLower()) - c1 = c1.toLower(); - if (!c2.isLower()) - c2 = c2.toLower(); - } - int r = QString::localeAwareCompare(c1, c2); - if (r < 0) - return -1; - if (r > 0) - return 1; - } - // The two strings are the same (02 == 2) so fall back to the normal sort - return QString::compare(s1, s2, cs); -} - -bool naturalSortLessThanCS(const QString& left, const QString& right) -{ - return (naturalCompare(left, right, Qt::CaseSensitive) < 0); -} - -bool naturalSortLessThanCI(const QString& left, const QString& right) -{ - return (naturalCompare(left, right, Qt::CaseInsensitive) < 0); -} - -bool naturalSortGreaterThanCS(const QString& left, const QString& right) -{ - return (naturalCompare(left, right, Qt::CaseSensitive) > 0); -} - -bool naturalSortGreaterThanCI(const QString& left, const QString& right) -{ - return (naturalCompare(left, right, Qt::CaseInsensitive) > 0); -} diff --git a/components/contentselector/model/naturalsort.hpp b/components/contentselector/model/naturalsort.hpp deleted file mode 100644 index 6973c55c2e..0000000000 --- a/components/contentselector/model/naturalsort.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef NATURALSORT_H -#define NATURALSORT_H - -#include - -bool naturalSortLessThanCS(const QString& left, const QString& right); -bool naturalSortLessThanCI(const QString& left, const QString& right); -bool naturalSortGreaterThanCS(const QString& left, const QString& right); -bool naturalSortGreaterThanCI(const QString& left, const QString& right); - -#endif From 1c1df996bea36ad78ad94c97bb018566829d6f3c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 22 Apr 2024 23:52:14 +0300 Subject: [PATCH 449/451] Allow running soulgem instructions on all actors (#7943) --- CHANGELOG.md | 1 + apps/openmw/mwscript/miscextensions.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caa3299c97..e935364264 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -170,6 +170,7 @@ Bug #7899: Editor: Doors can't be unlocked Bug #7901: Editor: Teleport-related fields shouldn't be editable if a ref does not teleport Bug #7908: Key bindings names in the settings menu are layout-specific + Bug #7943: Using "addSoulGem" and "dropSoulGem" commands to creatures works only with "Weapon & Shield" flagged ones Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 481a0e2ec1..cfdeb8c658 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -631,7 +631,7 @@ namespace MWScript ESM::RefId gem = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - if (!ptr.getClass().hasInventoryStore(ptr)) + if (!ptr.getClass().isActor()) return; const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); @@ -664,10 +664,10 @@ namespace MWScript for (unsigned int i = 0; i < arg0; ++i) runtime.pop(); - if (!ptr.getClass().hasInventoryStore(ptr)) + if (!ptr.getClass().isActor()) return; - MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) { if (it->getCellRef().getSoul() == soul) @@ -780,10 +780,10 @@ namespace MWScript ESM::RefId soul = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - if (!ptr.getClass().hasInventoryStore(ptr)) + if (!ptr.getClass().isActor()) return; - MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); + MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); for (MWWorld::ContainerStoreIterator iter(store.begin()); iter != store.end(); ++iter) { From 78eda530ac0ff2b45cf8eeb88c410f1a800f4110 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 23 Apr 2024 00:31:38 +0300 Subject: [PATCH 450/451] Simplify material file pointer acrobatics --- apps/niftest/niftest.cpp | 7 +++--- components/CMakeLists.txt | 2 +- components/bgsm/file.cpp | 28 +++++++++++++++++++++ components/bgsm/file.hpp | 3 +++ components/bgsm/reader.cpp | 33 ------------------------- components/bgsm/reader.hpp | 22 ----------------- components/resource/bgsmfilemanager.cpp | 5 +--- 7 files changed, 36 insertions(+), 64 deletions(-) delete mode 100644 components/bgsm/reader.cpp delete mode 100644 components/bgsm/reader.hpp diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index b391ce4bf1..0b8aa8e275 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include #include @@ -88,11 +88,10 @@ void readFile( } else { - Bgsm::Reader reader; if (vfs != nullptr) - reader.parse(vfs->get(pathStr)); + Bgsm::parse(vfs->get(pathStr)); else - reader.parse(Files::openConstrainedFileStream(fullPath)); + Bgsm::parse(Files::openConstrainedFileStream(fullPath)); } } catch (std::exception& e) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index df23121c5c..f8ccb1d62d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -108,7 +108,7 @@ add_component_dir (settings ) add_component_dir (bgsm - reader stream file + stream file ) add_component_dir (bsa diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp index 6b763321be..2aaacaf25c 100644 --- a/components/bgsm/file.cpp +++ b/components/bgsm/file.cpp @@ -1,9 +1,37 @@ #include "file.hpp" +#include +#include + #include "stream.hpp" namespace Bgsm { + MaterialFilePtr parse(Files::IStreamPtr&& inputStream) + { + std::shared_ptr file; + BGSMStream stream(std::move(inputStream)); + + std::array signature; + stream.readArray(signature); + std::string shaderType(signature.data(), 4); + if (shaderType == "BGEM") + { + file = std::make_shared(); + file->mShaderType = Bgsm::ShaderType::Effect; + } + else if (shaderType == "BGSM") + { + file = std::make_shared(); + file->mShaderType = Bgsm::ShaderType::Lighting; + } + else + throw std::runtime_error("Invalid material file"); + + file->read(stream); + return file; + } + void MaterialFile::read(BGSMStream& stream) { stream.read(mVersion); diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp index d3fb189256..b409752d74 100644 --- a/components/bgsm/file.hpp +++ b/components/bgsm/file.hpp @@ -10,6 +10,8 @@ #include #include +#include + namespace Bgsm { class BGSMStream; @@ -160,5 +162,6 @@ namespace Bgsm }; using MaterialFilePtr = std::shared_ptr; + MaterialFilePtr parse(Files::IStreamPtr&& stream); } #endif diff --git a/components/bgsm/reader.cpp b/components/bgsm/reader.cpp deleted file mode 100644 index eefc8b48b5..0000000000 --- a/components/bgsm/reader.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "reader.hpp" - -#include -#include -#include - -#include "stream.hpp" - -namespace Bgsm -{ - void Reader::parse(Files::IStreamPtr&& inputStream) - { - BGSMStream stream(std::move(inputStream)); - - std::array signature; - stream.readArray(signature); - std::string shaderType(signature.data(), 4); - if (shaderType == "BGEM") - { - mFile = std::make_unique(); - mFile->mShaderType = Bgsm::ShaderType::Effect; - } - else if (shaderType == "BGSM") - { - mFile = std::make_unique(); - mFile->mShaderType = Bgsm::ShaderType::Lighting; - } - else - throw std::runtime_error("Invalid material file"); - - mFile->read(stream); - } -} diff --git a/components/bgsm/reader.hpp b/components/bgsm/reader.hpp deleted file mode 100644 index 48508c9143..0000000000 --- a/components/bgsm/reader.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef OPENMW_COMPONENTS_BGSM_READER_HPP -#define OPENMW_COMPONENTS_BGSM_READER_HPP - -#include - -#include - -#include "file.hpp" - -namespace Bgsm -{ - class Reader - { - std::unique_ptr mFile; - - public: - void parse(Files::IStreamPtr&& stream); - - std::unique_ptr getFile() { return std::move(mFile); } - }; -} -#endif diff --git a/components/resource/bgsmfilemanager.cpp b/components/resource/bgsmfilemanager.cpp index 2d439ccc8a..7d93608603 100644 --- a/components/resource/bgsmfilemanager.cpp +++ b/components/resource/bgsmfilemanager.cpp @@ -2,7 +2,6 @@ #include -#include #include #include "objectcache.hpp" @@ -41,9 +40,7 @@ namespace Resource return static_cast(obj.get())->mBgsmFile; else { - Bgsm::Reader reader; - reader.parse(mVFS->get(name)); - Bgsm::MaterialFilePtr file = reader.getFile(); + Bgsm::MaterialFilePtr file = Bgsm::parse(mVFS->get(name)); obj = new BgsmFileHolder(file); mCache->addEntryToObjectCache(name.value(), obj); return file; From 0873eb6e62c4190017c3a6411bb61f5293e7e4f1 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 23 Apr 2024 11:49:33 +0400 Subject: [PATCH 451/451] Use scalable icons for wizard --- apps/wizard/installationtargetpage.cpp | 2 + apps/wizard/languageselectionpage.cpp | 2 + apps/wizard/methodselectionpage.cpp | 4 + apps/wizard/ui/installationtargetpage.ui | 3 - apps/wizard/ui/languageselectionpage.ui | 3 - apps/wizard/ui/methodselectionpage.ui | 9 - files/lang/wizard_de.ts | 20 - files/lang/wizard_fr.ts | 20 - files/lang/wizard_ru.ts | 20 - files/lang/wizard_sv.ts | 20 - files/wizard/icons/dollar.svg | 100 ++++ files/wizard/icons/folder.svg | 338 +++++++++++++ .../icons/preferences-desktop-locale.svg | 307 ++++++++++++ files/wizard/icons/system-installer.svg | 456 ++++++++++++++++++ files/wizard/icons/tango/48x48/dollar.png | Bin 7395 -> 0 bytes files/wizard/icons/tango/48x48/folder.png | Bin 1610 -> 0 bytes .../48x48/preferences-desktop-locale.png | Bin 1761 -> 0 bytes .../icons/tango/48x48/system-installer.png | Bin 2663 -> 0 bytes files/wizard/icons/tango/index.theme | 8 - files/wizard/wizard.qrc | 11 +- 20 files changed, 1214 insertions(+), 109 deletions(-) create mode 100644 files/wizard/icons/dollar.svg create mode 100644 files/wizard/icons/folder.svg create mode 100644 files/wizard/icons/preferences-desktop-locale.svg create mode 100644 files/wizard/icons/system-installer.svg delete mode 100644 files/wizard/icons/tango/48x48/dollar.png delete mode 100644 files/wizard/icons/tango/48x48/folder.png delete mode 100644 files/wizard/icons/tango/48x48/preferences-desktop-locale.png delete mode 100644 files/wizard/icons/tango/48x48/system-installer.png delete mode 100644 files/wizard/icons/tango/index.theme diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index dc94d2d002..1da28b1237 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -19,6 +19,8 @@ Wizard::InstallationTargetPage::InstallationTargetPage(QWidget* parent, const Fi setupUi(this); + folderIconLabel->setPixmap(QIcon(":folder").pixmap(QSize(48, 48))); + registerField(QLatin1String("installation.path*"), targetLineEdit); } diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp index 7dcf642dd6..7d4c2184ee 100644 --- a/apps/wizard/languageselectionpage.cpp +++ b/apps/wizard/languageselectionpage.cpp @@ -9,6 +9,8 @@ Wizard::LanguageSelectionPage::LanguageSelectionPage(QWidget* parent) setupUi(this); + flagIconLabel->setPixmap(QIcon(":preferences-desktop-locale").pixmap(QSize(48, 48))); + registerField(QLatin1String("installation.language"), languageComboBox, "currentData", "currentDataChanged"); } diff --git a/apps/wizard/methodselectionpage.cpp b/apps/wizard/methodselectionpage.cpp index b3a1c73635..2ff7db5487 100644 --- a/apps/wizard/methodselectionpage.cpp +++ b/apps/wizard/methodselectionpage.cpp @@ -11,6 +11,10 @@ Wizard::MethodSelectionPage::MethodSelectionPage(QWidget* parent) setupUi(this); + installerIconLabel->setPixmap(QIcon(":system-installer").pixmap(QSize(48, 48))); + folderIconLabel->setPixmap(QIcon(":folder").pixmap(QSize(48, 48))); + buyIconLabel->setPixmap(QIcon(":dollar").pixmap(QSize(48, 48))); + #ifndef OPENMW_USE_UNSHIELD retailDiscRadioButton->setEnabled(false); existingLocationRadioButton->setChecked(true); diff --git a/apps/wizard/ui/installationtargetpage.ui b/apps/wizard/ui/installationtargetpage.ui index c87214d8d9..4b1f4238d4 100644 --- a/apps/wizard/ui/installationtargetpage.ui +++ b/apps/wizard/ui/installationtargetpage.ui @@ -30,9 +30,6 @@ 0 - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Qt::RichText diff --git a/apps/wizard/ui/languageselectionpage.ui b/apps/wizard/ui/languageselectionpage.ui index fccd2aa424..3cff2c5b44 100644 --- a/apps/wizard/ui/languageselectionpage.ui +++ b/apps/wizard/ui/languageselectionpage.ui @@ -30,9 +30,6 @@ 0 - - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Qt::RichText diff --git a/apps/wizard/ui/methodselectionpage.ui b/apps/wizard/ui/methodselectionpage.ui index c2dd260527..28755ad438 100644 --- a/apps/wizard/ui/methodselectionpage.ui +++ b/apps/wizard/ui/methodselectionpage.ui @@ -59,9 +59,6 @@ 0 - - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Qt::RichText @@ -124,9 +121,6 @@ 0 - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Qt::RichText @@ -191,9 +185,6 @@ 0 - - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - diff --git a/files/lang/wizard_de.ts b/files/lang/wizard_de.ts index 33c7a5fb53..c3ef55b04a 100644 --- a/files/lang/wizard_de.ts +++ b/files/lang/wizard_de.ts @@ -128,10 +128,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Where should Morrowind be installed? - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - - Morrowind will be installed to the following location. @@ -170,10 +166,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov What is the language of the Morrowind installation? - - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - - Select the language of the Morrowind installation. @@ -197,10 +189,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Retail CD/DVD - - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - - Install from a retail disc to a new location. @@ -209,10 +197,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Existing Installation - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - - Select an existing installation. @@ -221,10 +205,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Don't have a copy? - - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - - Buy the game diff --git a/files/lang/wizard_fr.ts b/files/lang/wizard_fr.ts index 6c655885d2..010883d0c4 100644 --- a/files/lang/wizard_fr.ts +++ b/files/lang/wizard_fr.ts @@ -128,10 +128,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Where should Morrowind be installed? À quel emplacement Morrowind doit-il être installé ? - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. Morrowind sera installé à l'emplacement suivant : @@ -170,10 +166,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov What is the language of the Morrowind installation? Dans quelle langue est cette installation de Morrowind ? - - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. Sélectionnez la langue de cette installation de Morrowind. @@ -197,10 +189,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Retail CD/DVD Copie CD/DVD physique - - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. Installer depuis un CD/DVD et choisir la destination sur l'ordinateur. @@ -209,10 +197,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Existing Installation Installation existante - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. Sélectionnez une installation existante. @@ -221,10 +205,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Don't have a copy? Vous n'avez pas de copie du jeu ? - - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game Achetez le jeu. diff --git a/files/lang/wizard_ru.ts b/files/lang/wizard_ru.ts index 5784b11eac..ba9815f634 100644 --- a/files/lang/wizard_ru.ts +++ b/files/lang/wizard_ru.ts @@ -130,10 +130,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Where should Morrowind be installed? Куда нужно установить Morrowind? - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. Morrowind будет установлен в следующее место. @@ -172,10 +168,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov What is the language of the Morrowind installation? Какой язык использует ваша копия Morrowind? - - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - ><html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. Выберите язык, используемый вашей копией Morrowind. @@ -199,10 +191,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Retail CD/DVD CD/DVD-диск - - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. Установить игру с диска. @@ -211,10 +199,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Existing Installation Установленная копия игры - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. Выбрать установленную копию игры. @@ -223,10 +207,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Don't have a copy? Нет копии игры? - - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game Купить игру diff --git a/files/lang/wizard_sv.ts b/files/lang/wizard_sv.ts index 7f2f4f67fc..099a95d128 100644 --- a/files/lang/wizard_sv.ts +++ b/files/lang/wizard_sv.ts @@ -130,10 +130,6 @@ de ordinarie fonterna i Morrowind. Bocka i denna ruta om du ändå föredrar ord Where should Morrowind be installed? Var ska Morrowind installeras? - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Morrowind will be installed to the following location. Morrowind kommer installeras på följande plats. @@ -172,10 +168,6 @@ de ordinarie fonterna i Morrowind. Bocka i denna ruta om du ändå föredrar ord What is the language of the Morrowind installation? Vad är språket på Morrowindinstallationen? - - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/preferences-desktop-locale.png"/></p></body></html> - Select the language of the Morrowind installation. Välj språket på Morrowindinstallationen. @@ -199,10 +191,6 @@ de ordinarie fonterna i Morrowind. Bocka i denna ruta om du ändå föredrar ord Retail CD/DVD Köpt CD/DVD - - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/system-installer.png"/></p></body></html> - Install from a retail disc to a new location. Installera från en köpt skiva till en ny plats. @@ -211,10 +199,6 @@ de ordinarie fonterna i Morrowind. Bocka i denna ruta om du ändå föredrar ord Existing Installation Befintlig installation - - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/folder.png"/></p></body></html> - Select an existing installation. Välj en befintlig installation. @@ -223,10 +207,6 @@ de ordinarie fonterna i Morrowind. Bocka i denna ruta om du ändå föredrar ord Don't have a copy? Äger du inte spelet? - - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - <html><head/><body><p><img src=":/icons/tango/48x48/dollar.png"/></p></body></html> - Buy the game Köp spelet diff --git a/files/wizard/icons/dollar.svg b/files/wizard/icons/dollar.svg new file mode 100644 index 0000000000..60c5a4b23c --- /dev/null +++ b/files/wizard/icons/dollar.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/wizard/icons/folder.svg b/files/wizard/icons/folder.svg new file mode 100644 index 0000000000..a976bad226 --- /dev/null +++ b/files/wizard/icons/folder.svg @@ -0,0 +1,338 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + 2009-01-13, 2020-12-18 + + + Jakub Steiner, Daniel S. Fowler + + + + https://tekeye.uk/free_resources/tango_desktop_project/index + + + folder + directory + icon + + + + + Public Domain + + + + + https://tekeye.uk/ + + + https://tekeye.uk/free_resources/tango_desktop_project/images/folder.svg + computing + A representation of a folder for file storage. + en-GB + https://tekeye.uk/free_resources/tango_desktop_project/images/folder-remote.svg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/wizard/icons/preferences-desktop-locale.svg b/files/wizard/icons/preferences-desktop-locale.svg new file mode 100644 index 0000000000..51098cfa13 --- /dev/null +++ b/files/wizard/icons/preferences-desktop-locale.svg @@ -0,0 +1,307 @@ + + + Locale Preferences Icon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Jakub Steiner, Daniel S. Fowler + + + https://tekeye.uk/free_resources/tango_desktop_project/index + + Locale Preferences Icon + + + locale + settings + preferences + location + flag + computer + icon + + + 2021-01-02 + + + Public Domain + + + + + https://tekeye.uk/ + + + https://tekeye.uk/free_resources/tango_desktop_project/images/svg/apps/preferences-desktop-locale.svg + https://tekeye.uk/free_resources/tango_desktop_project/images/svg/apps/preferences-desktop-screensaver.svg + en-GB + computing + An icon for locale settings. + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/wizard/icons/system-installer.svg b/files/wizard/icons/system-installer.svg new file mode 100644 index 0000000000..7d95eea080 --- /dev/null +++ b/files/wizard/icons/system-installer.svg @@ -0,0 +1,456 @@ + + + System Installer Icon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + System Installer Icon + + + Jakub Steiner, Daniel S. Fowler + + + https://tekeye.uk/free_resources/tango_desktop_project/index + + 2021-01-02 + + + Public Domain + + + + + https://tekeye.uk/ + + + https://tekeye.uk/free_resources/tango_desktop_project/images/svg/apps/system-installer.svg + https://tekeye.uk/free_resources/tango_desktop_project/images/svg/apps/preferences-desktop-screensaver.svg + en-GB + + + install + setup + packages + manager + settings + preferences + cd + dvd + computer + icon + + + computing + An icon for system setup and installation. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/files/wizard/icons/tango/48x48/dollar.png b/files/wizard/icons/tango/48x48/dollar.png deleted file mode 100644 index a14ba2505d1d09b17fc3572af93b15ad7de7abd4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7395 zcmeHKc|4SB`yXWq$;B%?bwBqLN3gY!5K|BX zfj|;?OH+H`AI`s42?M{*D}IjwfmZ#o$I*prPvn7FY$lB!Km~KdSX3}IluiSILc7LX z+%q;7ubKCetrAp|iXEQRdo$jzb-N4vnI=O zF6`{fKJ}>_FO|(WIWd~_ASq{UI_J#II}*4pMKSI>WFJ;*T?(5S9Eel)^0CPcXV|^* zOIl2lACYMA_C6)h=!=~s8eV|a5Q3yz*;0Czs)KHiQn+p_3{SlpVwSO!$~?}G-2Svu z!zCbT`|6%USrrRyL8)*0YbB-vb7n^G@dmH&e-$oXx$n8cyN>J_p3Y{&Y}-Puir+N# zIzeHb=Qr&|-iREnGZ0b)kzkx5c=@bsD_`w#Dtf@u`+k zSkh*kGpp9w9h7oAj!Bi69G?E`*jiXF<_RG3rsI<-{z$kuFB zDGur;emr7GeN(!}rDOJ*>&i202FBt=`~rK76Kvf4YUrBfA&2dfJomh-DIG0%-mr1? zP+W?+GVWec+Gb;VvxfFVz2FR2Ipe@6+2duG-ap;#biLAiJa4x9&2!hY;{J76l_JyW zI(6C8RIy4fq*;FbpryB-;>uONx{vDY6Msv@4L*=-u8@jOCk?GeHsAElX{sE#_fn3- ztE*YR7>m9Ef0KBvz9C2Z-LLSEC#$!VNo)_iCdtsIbhjGL3Uo_KT9=o#uet0s{?g9t z6Z4GZ!Uha?RnW8dZstcEJrdie+5X5;x2?#n%A@}IB*}x`UDy3QpKi79Fa^K&D{f7k zllM}LyjO9L9V}Q@xw(sq#l3E?d6WXr_DKk@j4rx(!z7~}p^{wn*eGrREEVf7p6Fqg?WB$s_ z%vwv8Qv-hCejm@Y3Tg5LMx`~gZr*&OJL3B$miTN9VWgNko^V3Z(Dpod@`~4m*V3u+ zt~zLhFuG?v<5sTrRr|skh+E8>wR01`231y)CAo;P3dj1!`Et)_n?S}~@SdpM)n1|f zE4%u}*!3!rA46+{jpnj;qkZdR&uaFLkDMA@NE-<`zOYdEWI^}newml!JErGDxC!Cb zg!=Sc(`zS`CLQw{ROHX_60WUH*btRsm69nOzO&VX?2N*(j-Ef7BNUW!<*I=8q(weu z93lJmNWa33ng@b($S0quzKPlKc8OQU$@1M9U26TSpG3fmWt~2yXc~FV7)ezqyozj` z0u8GfWnsYEc1;xcMX*v@6R-$U)I9=cg{o-kB{-J=6`KWJzGZ{m{>vt8VZPZ6=6bvV z{3%*LKPgJ0$@VZv<{7q^i1%V$JyzV4Dt7upTttdU{6M0MqD**>KFVvSSg0iWtVr=i zW66e~gY5H(LrG_o9CdXgUYprT`D`8#Ms4iL-WeGsDZ8q|Xh4iDt-mbe3(8mW6fx&S-;h;&uB?;|`2af_!-#gl1anRno~nM@Hhvj8 z%<4HQ6MV(w%oXUaEmzf?-}wnn(Zt?5x*uAzPxrl9Zh=YeU3j_BB}pt)4y)gFT2&Ng zNV;^oH4E9?^S+}iq-eP9SeukX`j}(71FZKddOYun>3*wQ0zQUL4!O10*GX4myB@YLI; zMz!Y~#*^Po`u zXXb>u#*0$=+-FrS(OngF7IT48z0G;yiD%H6^_TBIDa7nK^r75y{7v`*w)fGkIi_iK z)#PWzCu8HJ5%IV0+kP85bv4ofzsotq@kQ;Uy7@C7bUS)tbP&{Dho>5XtWJG>fgTkl zrA3>WQvEZa;tyUL$)knWV^ByCNFa)CVnV>1nEZJP0?s};`%?5R8x1$y_HrnI$ciKs zVJJnm>+<7c)0|GPk~J=GEje_zz^gT16(1Dzi--7{{o9}pbMayW6NAuJcuBvxs)*H(}AR47iRrXqB{89PyV zQ`>~L6>GNvvThn$b_uSTsW7D$F7~T)N8qsL-*~Mj;FR2PSLXHPODxg& z%p-1U5zgZteY`mM%nr1u%_=}I3@gAF@Ov&7Mc`zS0dfIjN7dlIDV zwt+yB33T9C>SAM!BQpcFh!iG?sudc@0*-{|h`|mqy-*xL5J=?`!J&Zx3=S?-AF{-Y1Md0V zFbH@F!u8jOxY!WDCQLRJjM74B!GR5g9)g7I5CiM6DKwnDsrgq5prsG-<#Jg#7>vi` zY4NnRm~0;y0*l4M;7Aw}2?Y>PP8fqr423c{8~GGpI83P=GMmog(wPh}pOZ*p26Ocx z5MUhql^hr)n}q_MUmftn@|hGdNr#gG}X90pwbAfCoW^lXS>r zG!#oipr9xn9U_#70(dBBB%Fpu=xCGC%PH*Gbf6N50n4M}Q&9jaB8sSsM4&KGw6=~m z6ou6#K}lrb4vr*K;W|X%poP>~iiW=qI3ogHAA;0^fA1j#5V&#zY_3Qd1B1ci?|xa`0vH82>k(@6c~7MreNmon2mdOrV=HG!D^riJ>LkgfTNd*QkZ2~wV`HTDopkVG^UiPR;N z$mk#0IZPUtM`Tlte1IGP))^?RrF90YFJ+&`k7%AR703}NTn7h-L%u98SP#bEQ2(5s z9)HWRvB51DK##vA;qd&-a|mX!0_arskHY+!DE|v?nf=$K{7>e~VPC9Gn5;0MXnnbM zJjUO;{|n$325UN*%HS~nF7)M)FS0Cq?f@}==>wi;z~d42?fLjME%=r5U;KPcxBsFC zfcmGCf28kExqiy^j}-Vv;Gfy`Q?7rcz&`^2%&z}8xx~J|VN)5vL5~N#KdT{FV~A)oR$OppwuKTau%^YtE2`wU^#?>%wEtlKQ?V zR>=aPIQ94E`BG8(qwQ4=D&EsiQO50Mj4 zSaIM$ly`5p@Xgp^;$ z;p3j(Q!0{4bsalPHs3DU07@-Vef;a@6U8NXjgs~!I)a-XTRqJ`b<-w&_aBifx^arG zP3V%hr1-$2Ij*N5$GUUQ?cB^P#S1t_kxt~!-4Zgr=y`gh?Z9fo?%OV-`##lPR<=>B z6>puHDxB6_5jX+A$xzA5z0yfctO9lHU%-_%dqzx@b7MSP_L8fR&DX@Ggll2vT>6?Z zVp2kbaZ`GwcZj1}hBDPEKFR`*U8)6owI9#=?KVx<)7)%#Ev=RyqtgSupI|Ek_R3(! zq|%31KJ$!>t@Nm~9gv}WNXI~5YVCDbY-@V^ao^D8M@1)Q1oO5!jzyl^>C&e!9Xi)t zRV(jey|+?PIQI_eh#O~h)SZn5CEo29G@ciURp57rYE4}9(~=pxonkNh;gH3G9DD8| zzB=iJL_>RfynI#d6`o|?+tepY%_{e|daAs4tT?%1g!gJhc|zPxy%IMkSP~!NIW*sX nX6yLN#Ru6s{y+*Wrhf*#?j9b?dCi;y5)6blvo)$Qp1=mSL zK~!ko?U~DqWJwjqe-ZbQkIIKN(@NvQh5aXpOL5^W8pPGW;L?rD?gl|-(F>P0xaw}) z%tG)F(4~zi!fZqvM22Bt#Dy>vgWXnERc7A2_r~+M=zDXrvZ^byYUoOoIuPW^xWwiA z;yWkeL|nMB8@sXpX;Z6z9zJ~d6Q$JqB9a0qr99voKPaVa<42{GSs7XZgb z=+U39i$J>iM?`*h_wL%jxO;fG4 ze)sy z2wSPJ9#Xe&-yZdPJ@)ta>G%6+tx-y$)Tu>}hSc&|2!UtMo_+7$y?eiY`t<4S(gZ>X z)>_N)@i9k7N5mK}rNU|`0UR70EJ3#*+orW%5^McD&jCVF6k}j^6$NVN9HY@_Is2;w zHmR^qpatIkt}4;`Ns=rHXsr>En5Jp(sv%T+@0rbJ93CDn0l!X#^(0;gze=JdvOepp zewJkb(HPUcDuK#5$8b2rT1yCl-~H;Go+S#O ze{<;b?2q4l@B_R49_PcSfBxkU-?=CUDr+r8Q81ZI2q7@7Z}HO~eTQK$yROYu17BV5 zENjotfA!(pTPZLY45+FK<07Ew zKBeE)2(mNq08xa>c(i76o?NR6l&u1z(TKD=V$|>8{qDf~ASk7%YKId2;z9zg0!2}9 ze0n3j%yzk|Ex5Ng54D9!S!DJ$zlc)2NwYin(66;NTo zpx432cRPCIjEK81NHQh{MLuh97RlNEjXI61woCSctsXCWKs? zrrH?8yw;>|=Tt79jd|G?sBG?)&E<4iJG?h9HxcL_92|7-+_{rvS%&KLQAvuife_@& z&FCdM*B_e@S(Mhfay|z}Kmo*c0y(fZola8`NsKX6Zop6!#%)esx%7kWLrk2oFvh!j zRsn-%==?kao%wvOVvI?aWq933rwL(qhq2{x($prH*^3F#0Su6}-!1f74v11J{JTgf zLr$;X-7R{7>B(G7ZNuk(|LsT3{fk=tFw-jFn`VtMAN=*jTR#q(?30iG$<8hMmoGp0 z!~50bVAfm~y9VnQ*Gi2IfIXlO^cuO6MkTe2^L%^K1!|ydTG0?WLja(=0zPdBBs(Gy zn$^ZPfK5XnoVnOLuZC(8MX&D_*BLww*wCmT+}MrX*#DpX7iXTUrIOxbGynhq07*qo IM6N<$f?B=}0ssI2 diff --git a/files/wizard/icons/tango/48x48/preferences-desktop-locale.png b/files/wizard/icons/tango/48x48/preferences-desktop-locale.png deleted file mode 100644 index f56497bd23989040f0a2d23080b8103ad086cfbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1761 zcmV<71|Io|P)f8XQYxp&4h_IP4DiIX;ILTG3tgaC<(1skfaDuhs3pe_p%ELpSg zo((HNVgnl%tyF=ET2vukf$$J%)uuF+lR9pi_-)U_Gk5Oed$8~jEaW&-M~1PKBVFB@ zJ6D?X`@VDj=bR57*&}->6Z`%6!sovINg>SBGSFX?LOm|v1W?9%oO9L-(FVU^jd|C@ zycGTeTlfzyymk7cIp*B|1>pIoo)6{gZIO`BjAP`8Q$chZ>4Axt`a)fa}RE?qJr6vv%WPvw2}>zDue!|r|n zdiNJ4)Xy({_Q|QK6H|0HIxMU&5(Xhb5D)@T^e?8{>BTh3zgA>6H<_{48s#u0mG7*mkfBA|CDDsB3&Jow(6;uv3F&4^mZvDGP*M|UO%%n%QA}707#ojbyo_-kZ+3z~58l;EvGC#T zt}Pz?U;2Ng7T$aEEXNg&G*M`&?>VcaMyZ3jB9FR^%DppN9BqIjzXL}b!22TwpzmAW z_TlitvK8N8y*5vK>?T>MNh&*dX~BbV8VZffG`?BGY>eZ3Rp?EidUZe@GQfSTueNx5 z;#EG3|H5qJERWAV!R*wtjK>YeN|Qu-44kB}1*XX9q+6_RFS6dc!|LV=&GsT&=^C<7 z1f5C3%^9NYX`=2lQ95-Xu%Q8N)?eqwKbkWdQXc86EXzPV7|=qDn-0EFRerNADFM)%qCYljDpxYIqN6FU8?U9>D&a zLIsK&?=6@+x9%2cKMTEZEB-})auj7~H-G_n^UrUmX+JGGY3H@u8w1qg+fp7qz9z3Is2*A~) zs|h=!i5mc@!8>9nKitSTe*G0+As=Yf%%8@xjT@?(XD_x@fwNTDdJIu$iZMNbf_F|-J<(vhh? znM~-*Z4%L;ZB{8L4qt`#v3v3}89qiMJWWM6=x1HJ?S$>TiM1O@uZ%zwm#VT7HstZ} zJeBe|VJ)JauhC5JvejRr*lpFaXY5`fbmZyMIhqR+fafwDW{p z`L4hvwPFt$LxtH?f7Um$md(V@%!t{&}?jgxO!+%$cd3Sbm2BIP#{3;NJy+iHYw_2o0Le3mZ)8l zyWDe|nLKdGP$acW$r6$R8enj4Gv|E&@BiIq4%}+D+O78gx`~YrJb3Wn-+_I}+Ktb- z#uiH{zwyc|ul(SK19;|{XZ|LW$$W2M;J%bpYVAve_|#gf1tGsV{+n751b=n-@ZtZw zVE_&uJow#wKKBoUy9P6-PK{D7mv4&tJ3Bk+=;-9=FOSt~wc0mdef8B>H!@E)5&j!( zZEgSb@WY?WoIZV;nVFfJX3KMPb4aNe7#MITC#Rkm7#Ns(@4fflyKVr_KKtxnx3;$a z+upr<-IFIr?S%`|b;7Qxi)$G_J3EI`iU%KjP)$rs?7#Qkd#lHfAOA%o0P1QFJkR_4 z=bwKb*LAu7{`=RMnza@o1fJ)uJKtK{@Ve3XR4T=;UAs7R=+JY(KVKIBAeYPKn4O(n zSAa^T!n^Oj%Yg$2Zi-2@w6x&5uG?(fRRL(NuMm7S0MGO2@9)2P@m&b4wM~PJ)8GYW zN$~Z05CjYl58u=zwGjYUWxyD-EbMxA>ia%BcI^0rC)!2~*sE!OiGh)kk(;UgrjFmt ziS25wuRQlWkG{UXKVAmbv;UR%ZZ-oO1)v%TZ`Y%7>H2yYu+}m)HO1-Erzw}q)M_=7 zB*FK6GMNk=9Ub)c_L9wJKfMz>j$<1oq%aH_85v=4aImT1T5C?6IKi=F$7pYFr@OnG zuC6Z9=`^nE62~z?5HK}0#cQvo||Qc8pnXsxd} zuKZS#BuFWF^wCGT+6 z?OgcQxA^lff0-zXD3wYWW0p;3X#uqI=y~21AdE32NkVULub7{o?|kQ-cit=(i+hU2 zqPgZoww^h;u1kMU&$@GGzWP=6KK?kK=TWUzQA#aq+ggj(24fbFx_*>WVJ&Mw7-JY5 z9CQkW!k;*f^Ox7;1C9J33`2%bomw+~Vf%Jk`upkb?xs?y;JPkKDHaP$L81*&oG@Rh za_Mr3OP4M)Q!10x!59n1Sj>uiv#_who;`a!rPRL`i$!mv02-DOp6AiGZQGhjmCt{k z&pq-8Q4}GiToztRg>qdMY5|vKE;BnVN4twYC+6=Wq~*`7z;_FsZ^>gEG!Vm zG41W`+TWYnCL|e2KRB9pB zT7=`K&^SbiWxi6QJX>Mra+z8Xl4wIx54k&b>`3{(|FwVcKy!4OsJiLcmkPs(^Y&N$Tp2hDYS0$PszqzyaORgc_O8 zk`MIWefO0mLq1QgtqsR<7VTaslu|fSVG%geMK~_iN`-t~cmRxvp!QI`Qg6wmKiI$AFxhV}5oPRoDDdmrCJ! z{vs1X(z&^l%YXL!U}AiqOnVDST)~wZhnO%jM0FMty_g8YFm!u*dOq2Z6E~|y zM?d9m8@Av2eznXlAv-87lB!kQR_L#!oA3mjpYoSd}A znE&2L6q3dm(Wsmb!;n*BV^=UB;+V|%IOootW9!zfSPN3Q#EB({4cbaF89@?hN;75V z=E}^L126$r*YI6QI_o1yFeU~^Vyz^O1=5k6J$p9LTK`wGMCJpVthJ(16mngc?Ok1K zH>7^{GydnzHaDv6Q~zUPt2wjh;DCY@b0t!$P}g#v{_8$wu&)__6l1Zxex zvRwRRnsT{33H)y(020UoEr4j2iBFG@uK|!6A16L}lGCS7;Wz@N76C}X4R+*cy$&;vubqv=JnSZ zn|}C1esbgprBVsU5lF{DIs#WYxXK~XAf&(89S=kbD|~QL2*(GZh?7M*3t7p@k>R9N zD*e1zEdIFJdSGb{DvU8v6h&;lijdY?x^KIUxngncdLT(?3q$_n``_p5&pk&=E?3XU z23I)SRt^&0Y~FF8plFw+GXtH56zh~XD+_}`s?4RD>!bnZJ~fxpmpEA zeP8P7>G@tRmut9xs?d|O%l}aImpi-$&E|)of{=A-;n4ns%o_+i6xBvavv17-9 z5^xEq)`gF*W#>!Q0my3vu=MU!9Ym_$(~mv&*v|g`{y%MPZQbU1-e-gmev%}!K@eP= zn3y - - icons/tango/48x48/preferences-desktop-locale.png - icons/tango/index.theme - icons/tango/48x48/folder.png - icons/tango/48x48/system-installer.png - icons/tango/48x48/dollar.png + + icons/preferences-desktop-locale.svg + icons/folder.svg + icons/system-installer.svg + icons/dollar.svg images/intropage-background.png