From 69bb65e47bd3c6dcd37beb1535f24034274fb339 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 13 Aug 2023 16:51:15 +0100 Subject: [PATCH 001/246] 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/246] 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/246] 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/246] 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/246] 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/246] 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/246] 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/246] 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/246] 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/246] 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/246] 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 55285b5e57d60ecc16fb3f6cbea079f3e260e9dc Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 3 Feb 2024 10:43:38 -0600 Subject: [PATCH 012/246] 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 013/246] 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 014/246] 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 86666761a3c037b399ae1b6e522863f444184d06 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 8 Feb 2024 21:51:54 -0600 Subject: [PATCH 015/246] 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 567d36240eec88d8b1ae200e0911a94e4da86fc1 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 12 Feb 2024 15:02:21 +0000 Subject: [PATCH 016/246] 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 91e7eebefb2c0d9847d67258da27f0d834fa4f46 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 14 Feb 2024 13:32:39 +0000 Subject: [PATCH 017/246] 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 9c959d9698ff05adc493c0e8d57282d0b6d1cf94 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 16 Feb 2024 01:33:52 +0000 Subject: [PATCH 018/246] 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 6e81927d60d7df17195f16f8436430f173443807 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 16 Feb 2024 13:06:48 +0300 Subject: [PATCH 019/246] 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 020/246] 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 021/246] 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 95ade48292eb922579334c20d2d7bdafd755fad7 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 16 Feb 2024 19:22:34 -0600 Subject: [PATCH 022/246] 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 023/246] 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 41d41780a836b5e7f7ec4ffd052c86c945d17f8c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 18 Feb 2024 13:16:47 +0300 Subject: [PATCH 024/246] CharacterController: rework movement queueing logic (#7835) --- apps/openmw/mwmechanics/character.cpp | 88 +++++++++++++++------------ 1 file changed, 49 insertions(+), 39 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,34 +2416,41 @@ 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 (speed > 0.f && movementFromAnimation != osg::Vec3f()) + if (isMovementAnimationControlled()) { - // 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; - } + if (duration != 0.f && movementFromAnimation != osg::Vec3f()) + { + movementFromAnimation /= duration; - if (!(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) - movement = movementFromAnimation; + // 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; + } + + movement = movementFromAnimation; + } + else + { + movement = osg::Vec3f(); + } + } + else if (mSkipAnim) + { + movement = osg::Vec3f(); + } if (mFloatToSurface) { @@ -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 025/246] 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 026/246] 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 027/246] 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) { - }; + 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; - }; - - template <> - struct ArchiveSelector - { - using type = BsaArchive; - }; - - template <> - struct ArchiveSelector - { - using type = BsaArchive; - }; - - 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 028/246] Convert BsaVersion to enum class --- apps/bsatool/bsatool.cpp | 20 +++++++++++--------- 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, 33 insertions(+), 31 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: - return call(info); - case Bsa::BSAVER_BA2_GNRL: - return call(info); - case Bsa::BSAVER_BA2_DX10: - return call(info); - case Bsa::BSAVER_UNCOMPRESSED: + case Bsa::BsaVersion::Unknown: + break; + case Bsa::BsaVersion::Uncompressed: return call(info); - default: - throw std::runtime_error("Unrecognised BSA archive"); + case Bsa::BsaVersion::Compressed: + return call(info); + case Bsa::BsaVersion::BA2GNRL: + return call(info); + case Bsa::BsaVersion::BA2DX10: + return call(info); } + + 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 029/246] 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 030/246] 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 dc5371d157833b5cc834cfcaf5e460dc3af26504 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 18 Feb 2024 22:28:33 +0100 Subject: [PATCH 031/246] 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 032/246] 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 033/246] 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 034/246] 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 035/246] 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 | 147 +++++++++++++++++-------------- apps/openmw/mwrender/ripples.hpp | 18 ++-- 2 files changed, 93 insertions(+), 72 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::updateState(const osg::FrameStamp& frameStamp, State& state) + { + state.mPaused = mPaused; + + if (mPaused) + return; + + 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) + { + state.mPaused = true; + return; + } + + 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)); + const osg::Vec2f offset = mCurrentPlayerPos - mLastPlayerPos; + mLastPlayerPos = mCurrentPlayerPos; + + state.mStateset->getUniform("positionCount")->set(static_cast(mPositionCount)); + state.mStateset->getUniform("offset")->set(offset); + + osg::Uniform* const positions = state.mStateset->getUniform("positions"); + + 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) { - if (!nv.getFrameStamp()) + const osg::FrameStamp* const frameStamp = nv.getFrameStamp(); + + if (frameStamp == nullptr) return; if (nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) - { - size_t frameId = nv.getFrameStamp()->getFrameNumber() % 2; + updateState(*frameStamp, mState[frameStamp->getFrameNumber() % 2]); - const auto& 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); - - auto* positions = mState[frameId].mStateset->getUniform("positions"); - - 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(); - - mPositionCount = 0; - } 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 036/246] Minor launcher improvements (feature 7843) --- apps/launcher/settingspage.cpp | 13 +++- apps/launcher/settingspage.hpp | 1 + apps/launcher/ui/settingspage.ui | 114 ++++++++++++++++--------------- files/lang/launcher_de.ts | 36 +++++----- files/lang/launcher_fr.ts | 36 +++++----- files/lang/launcher_ru.ts | 18 ++--- 6 files changed, 111 insertions(+), 107 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,45 +652,6 @@ - - - <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 - - - - - - - 3 - - - cells - - - 0.000000000000000 - - - 0.125000000000000 - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - <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> @@ -700,14 +661,7 @@ - - - - Viewing distance - - - - + 3 @@ -723,7 +677,53 @@ - + + + + Viewing distance + + + + + + + cells + + + 3 + + + 0.000000000000000 + + + 0.125000000000000 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <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 + + + + <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> @@ -1435,18 +1423,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <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 - - <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 - - In third-person view, use the camera as the sound listener instead of the player character. @@ -1455,5 +1435,21 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Use the camera as the sound listener + + Maximum light distance + + + + 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> @@ -1435,18 +1423,10 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <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 - - <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 - - In third-person view, use the camera as the sound listener instead of the player character. @@ -1455,5 +1435,21 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Use the camera as the sound listener + + Maximum light distance + + + + 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 037/246] 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 038/246] 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 039/246] 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 040/246] 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 041/246] 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 042/246] 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 043/246] 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 044/246] 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 045/246] 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 046/246] 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 047/246] 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 048/246] 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 049/246] 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 050/246] 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 051/246] 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 052/246] 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 053/246] 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 054/246] 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 055/246] 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 056/246] 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 057/246] 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 058/246] 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 059/246] 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 060/246] 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 061/246] 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 062/246] 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 063/246] 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 064/246] 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 065/246] 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 066/246] 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 067/246] 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 068/246] 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 069/246] 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 070/246] 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 071/246] 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 072/246] 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 073/246] 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 074/246] 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 075/246] 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 076/246] 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 077/246] 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 078/246] 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 079/246] 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 080/246] 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 081/246] 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 082/246] 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 083/246] 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 084/246] 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 085/246] 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 086/246] 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 087/246] 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 088/246] 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 089/246] 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 090/246] 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 6b07718871a616ec8d73ce882e3c6075571badfd Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 25 Feb 2024 01:01:55 +0100 Subject: [PATCH 091/246] 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 092/246] 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 093/246] 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 094/246] 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 095/246] 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 096/246] 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 097/246] 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 c82c111ee163d9a897b43764302ed29113403f99 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 27 Feb 2024 19:28:51 +0100 Subject: [PATCH 098/246] 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 099/246] 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 100/246] 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 101/246] 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 102/246] 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 103/246] 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 0519e1215f608fa82374a220ebefa182f25f05db Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 28 Feb 2024 17:20:46 +0100 Subject: [PATCH 104/246] 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 e54decc830fde07cdf95192e25f7689bdea34eee Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 1 Mar 2024 12:24:36 +0100 Subject: [PATCH 105/246] 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 106/246] 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 107/246] 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); + const float fCombatDistance = mStore.get().find("fCombatDistance")->mValue.getFloat(); + target = MWMechanics::getHitContact(actor, fCombatDistance).first; - // 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(); - - 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()) + if (target.isEmpty()) { - target = result1.first; - hitPosition = result1.second; - if (dist1 > getMaxActivationDistance()) - target = nullptr; - } - else if (result2.mHit) - { - 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 108/246] 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 109/246] 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 110/246] 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 111/246] 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 1499dd2654fa025448a715a41f32d2d21c566105 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 6 Mar 2024 18:16:55 +0100 Subject: [PATCH 112/246] Add getCompositeSize and handle NPC data --- components/esm/decompose.hpp | 7 +++ components/esm3/loadnpc.cpp | 95 ++++++++++++++++-------------------- components/esm3/loadnpc.hpp | 2 - components/esm3/loadpgrd.cpp | 2 +- 4 files changed, 51 insertions(+), 55 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); - } - else if (esm.getSubSize() == 12) - { - 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); + esm.getComposite(mNpdt); } else - esm.fail("NPC_NPDT must be 12 or 52 bytes long"); + { + 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"); + } 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 113/246] 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 114/246] 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 115/246] 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 116/246] 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 117/246] 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 118/246] 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 af8c2a94dfbc9292f014587eb8c2031a13a62a2c Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 18 Feb 2024 21:50:19 +0000 Subject: [PATCH 119/246] 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 120/246] 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 121/246] 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 122/246] 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 123/246] 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 124/246] 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 125/246] 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 504a9e7d4372de1ee9000b17a91b068b91125aee Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 8 Mar 2024 17:09:49 +0100 Subject: [PATCH 126/246] 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 127/246] 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 128/246] 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 129/246] 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 130/246] 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 131/246] 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 72cf015401f3b455b9860a617b1bf5d9312c75f6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 9 Mar 2024 20:18:41 +0000 Subject: [PATCH 132/246] 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 133/246] 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 134/246] 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 135/246] 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 136/246] 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 137/246] 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 138/246] 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 139/246] 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 140/246] 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 141/246] 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 142/246] 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 143/246] 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 144/246] 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 145/246] 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 146/246] 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 147/246] 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 148/246] 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 149/246] 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 150/246] 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 151/246] 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 152/246] 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 153/246] 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 154/246] 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 155/246] 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 156/246] 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 157/246] 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 158/246] 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 159/246] 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 160/246] 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 161/246] 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 162/246] 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 | 13 +++++++++---- 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, 77 insertions(+), 55 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 + +#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 - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable : 4706) #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 163/246] 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 164/246] 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 dd18e17c97d023669d06150634516a8a18b33cb6 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 15 Mar 2024 00:47:01 +0000 Subject: [PATCH 165/246] 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 166/246] 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 167/246] 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 168/246] 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 169/246] 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 170/246] 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 171/246] 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 172/246] 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 173/246] 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 174/246] 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 175/246] 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 176/246] 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 177/246] 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 178/246] 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 179/246] 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 180/246] 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 181/246] Use namespace instead of static class --- apps/openmw_test_suite/lua/test_yaml.cpp | 2 + components/lua/yamlloader.cpp | 442 ++++++++++++----------- components/lua/yamlloader.hpp | 38 +- 3 files changed, 241 insertions(+), 241 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 + }; + + sol::object load(const std::vector& rootNodes, const sol::state_view& lua); + + sol::object getNode(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + sol::table getMap(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth); + + ScalarType getScalarType(const YAML::Node& node); + + 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); } - 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) + sol::object load(const std::vector& rootNodes, 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); - } - - 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: + if (rootNodes.empty()) 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 (rootNodes.size() == 1) + return getNode(rootNodes[0], lua, 0); + + sol::table documentsTable(lua, sol::create); + for (const auto& root : rootNodes) { - if (!value.empty() && value[0] == '-') - return sol::make_object(lua, -std::numeric_limits::infinity()); - - return sol::make_object(lua, std::numeric_limits::infinity()); + documentsTable.add(getNode(root, lua, 1)); } - case ScalarType::Boolean: + + 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) + 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 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 (Misc::StringUtils::lowerCase(value) == "true") - return sol::make_object(lua, true); + 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"); - if (Misc::StringUtils::lowerCase(value) == "false") - return sol::make_object(lua, false); + 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"); - nodeError(node, "Can not read a boolean value '" + value + "'"); + childTable[key] = getNode(pair.second, lua, depth); } - case ScalarType::Decimal: + + return childTable; + } + + sol::table getArray(const YAML::Node& node, const sol::state_view& lua, uint64_t depth) + { + sol::table childTable(lua, sol::create); + + for (const auto& child : node) { - 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 + "'"); + childTable.add(getNode(child, lua, depth)); } - case ScalarType::Float: + + 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; + + 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) { - // 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); + 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()); - 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); + 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, "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); + if (Misc::StringUtils::lowerCase(value) == "false") + return sol::make_object(lua, false); - nodeError(node, "Can not read an octal value '" + value + "'"); + 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 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 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 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 182/246] 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 183/246] 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 184/246] 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 185/246] 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 186/246] 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 187/246] 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 | 278 +++++++------- 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, 2046 insertions(+), 2727 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 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 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("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 188/246] 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 189/246] 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 190/246] 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 37b695a0cfc50a23f975d74d57e411239879be82 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 Mar 2024 14:12:02 +0100 Subject: [PATCH 191/246] 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 da8150e2e4dcfca3e7fed213b402ed3fa7a6ae3a Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Thu, 21 Mar 2024 15:51:29 +0000 Subject: [PATCH 192/246] 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 193/246] 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 194/246] 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 195/246] 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 196/246] 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 197/246] 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 198/246] 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 199/246] 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 200/246] 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 201/246] 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 202/246] 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 203/246] 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 204/246] 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 | 39 ++++++++++++++++++-------------- apps/openmw/mwworld/worldimp.hpp | 1 - 3 files changed, 22 insertions(+), 20 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; - - if (const ESM::Region* region = mStore.get().search(cell->mRegion)) - return region->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 (!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 205/246] 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 206/246] 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 207/246] 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 208/246] 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 209/246] 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 210/246] 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 6d529835aeb2a7f7ae2f7e76ba5013f80e0c820a Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Mon, 25 Mar 2024 13:46:23 +0000 Subject: [PATCH 211/246] 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 212/246] 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.mId == id; }, ptr); + 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.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); - const ESM::RefId& getId() const { return mId; } + ESM::RefId getActiveSpellId() const { return mActiveSpellId; } + void setActiveSpellId(ESM::RefId id) { mActiveSpellId = id; } + + 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; + auto effect = rollIngredientEffect(mCaster, ingredient, mCaster != getPlayer()); - const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - const auto magicEffect = store.get().find(effect.mEffectID); - const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster); - - 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 213/246] 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 214/246] 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 2e6878633145a7dc6b6897a1110b9fb4817ce52e Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 27 Mar 2024 07:32:53 +0000 Subject: [PATCH 215/246] 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 216/246] 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 217/246] 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 218/246] 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 1360eeb839c67aaed6c787e2406e5d3fb29fbb6c Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Mon, 25 Mar 2024 16:55:19 -0500 Subject: [PATCH 219/246] 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 220/246] 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 221/246] 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 222/246] 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 223/246] 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 224/246] 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 225/246] 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 226/246] 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 227/246] Show F4 stats in pages --- apps/openmw/engine.cpp | 12 +- components/resource/stats.cpp | 639 +++++++++++++++++----------------- components/resource/stats.hpp | 45 +-- 3 files changed, 344 insertions(+), 352 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"); - std::string_view kwList(envList); + 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; - auto kwBegin = kwList.begin(); + 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", + }; - while (kwBegin != kwList.end()) + void setupStatCollection() { - auto kwEnd = std::find(kwBegin, kwList.end(), ';'); + const char* envList = getenv("OPENMW_OSG_STATS_LIST"); + if (envList == nullptr) + return; - const auto kw = kwList.substr(std::distance(kwList.begin(), kwBegin), std::distance(kwBegin, kwEnd)); + std::string_view kwList(envList); - 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") + auto kwBegin = kwList.begin(); + + while (kwBegin != kwList.end()) { - collectStatGPU = true; - collectStatEvent = true; - collectStatFrameRate = true; - collectStatUpdate = true; - collectStatEngine = true; - collectStatRendering = true; - } + auto kwEnd = std::find(kwBegin, kwList.end(), ';'); - if (kwEnd == kwList.end()) - break; + const auto kw = kwList.substr(std::distance(kwList.begin(), kwBegin), std::distance(kwBegin, kwEnd)); - kwBegin = std::next(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; + } - class SetFontVisitor : public osg::NodeVisitor - { - public: - SetFontVisitor(osgText::Font* font) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mFont(font) - { - } + if (kwEnd == kwList.end()) + break; - void apply(osg::Drawable& node) override - { - if (osgText::Text* text = dynamic_cast(&node)) - { - text->setFont(mFont); + kwBegin = std::next(kwEnd); } } - private: - osgText::Font* mFont; - }; - - osg::ref_ptr getMonoFont(VFS::Manager* vfs) - { - if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf") && vfs->exists(sFontName)) + osg::ref_ptr createBackgroundRectangle( + const osg::Vec3& pos, const float width, const float height, const osg::Vec4& color) { - Files::IStreamPtr streamPtr = vfs->get(sFontName); - return osgText::readRefFontStream(*streamPtr.get()); + 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; } - return nullptr; + osg::ref_ptr getMonoFont(const VFS::Manager& vfs) + { + if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf") && vfs.exists(fontName)) + { + const Files::IStreamPtr streamPtr = vfs.get(fontName); + return osgText::readRefFontStream(*streamPtr); + } + + return nullptr; + } + + class SetFontVisitor : public osg::NodeVisitor + { + public: + SetFontVisitor(osgText::Font* font) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mFont(font) + { + } + + void apply(osg::Drawable& node) override + { + if (osgText::Text* text = dynamic_cast(&node)) + { + text->setFont(mFont); + } + } + + private: + osgText::Font* mFont; + }; } - 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) + Profiler::Profiler(bool offlineCollect, const VFS::Manager& vfs) + : mOfflineCollect(offlineCollect) + , mTextFont(getMonoFont(vfs)) { - _camera = new osg::Camera; - _camera->getOrCreateStateSet()->setGlobalDefaults(); - _camera->setRenderer(new osgViewer::Renderer(_camera.get())); - _camera->setProjectionResizePolicy(osg::Camera::FIXED); - - _resourceStatsChildNum = 0; - - _textFont = getMonoFont(vfs); - } - - Profiler::Profiler(bool offlineCollect, VFS::Manager* vfs) - : _offlineCollect(offlineCollect) - , _initFonts(false) - { - _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; - viewer->getViewerStats()->collectStats("resource", false); + mCamera->setNodeMask(0); + mSwitch->setAllChildrenOff(); + + 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); + mCamera->setClearMask(0); + mCamera->setAllowEventFocus(false); - _camera->setRenderer(new osgViewer::Renderer(_camera.get())); - - _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 { - } - - void drawImplementation(osg::RenderInfo& renderInfo, const osg::Drawable* drawable) const override - { - if (!mStats) - return; - - 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); - - unsigned int frameNumber = renderInfo.getState()->getFrameStamp()->getFrameNumber() - 1; - - for (const auto& statName : mStatNames.get()) + public: + explicit ResourceStatsTextDrawCallback(osg::Stats* stats, std::span statNames) + : mStats(stats) + , mStatNames(statNames) { - if (statName.empty()) - viewStr << std::endl; - else - { - 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()); + void drawImplementation(osg::RenderInfo& renderInfo, const osg::Drawable* drawable) const override + { + if (mStats == nullptr) + return; - text->drawImplementation(renderInfo); - } + osgText::Text* text = (osgText::Text*)(drawable); - osg::ref_ptr mStats; - std::reference_wrapper> mStatNames; - }; + 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); - void StatsHandler::setUpScene(osgViewer::ViewerBase* viewer) + const unsigned int frameNumber = renderInfo.getState()->getFrameStamp()->getFrameNumber() - 1; + + for (const std::string& statName : mStatNames) + { + if (statName.empty()) + viewStr << std::endl; + else + { + 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->drawImplementation(renderInfo); + } + + private: + osg::ref_ptr mStats; + std::span mStatNames; + }; + } + + void StatsHandler::setUpScene(osgViewer::ViewerBase& viewer) { - _switch = new osg::Switch; + 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); - _camera->addChild(_switch); + 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; - 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 + 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 228/246] 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 229/246] 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 230/246] 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 231/246] 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 232/246] 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 233/246] 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 234/246] 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 36ca3b8bd923e826eff476a27729763dd71d6632 Mon Sep 17 00:00:00 2001 From: Arnaud Dochain Date: Mon, 1 Apr 2024 20:55:38 +0000 Subject: [PATCH 235/246] 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 236/246] 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 237/246] 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 238/246] 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 239/246] 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 240/246] 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 fbe84f4668c1fc3c51fe1e2139b71e99a67ead00 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 31 Mar 2024 23:08:47 +0300 Subject: [PATCH 241/246] 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 242/246] 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 243/246] 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 244/246] 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 8fecbb55ffca2d4edbd091b41ecb2aa3bdacfa3c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Fri, 5 Apr 2024 10:12:31 +0400 Subject: [PATCH 245/246] 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 b0008d22e6a049f13e6fe9889ac326792670632e Mon Sep 17 00:00:00 2001 From: trav5 Date: Sat, 6 Apr 2024 23:07:11 +0200 Subject: [PATCH 246/246] 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,